|
|
@ -13,6 +13,7 @@ client-serving data such as histories. |
|
|
|
|
|
|
|
import asyncio |
|
|
|
import signal |
|
|
|
import ssl |
|
|
|
import traceback |
|
|
|
from functools import partial |
|
|
|
|
|
|
@ -35,51 +36,62 @@ class Controller(LoggedClass): |
|
|
|
self.loop = loop |
|
|
|
self.env = env |
|
|
|
self.daemon = Daemon(env.daemon_url) |
|
|
|
self.block_processor = BlockProcessor(env, self.daemon) |
|
|
|
self.block_processor = BlockProcessor(env, self.daemon, |
|
|
|
on_catchup=self.start_servers) |
|
|
|
self.servers = [] |
|
|
|
self.sessions = set() |
|
|
|
self.addresses = {} |
|
|
|
self.jobs = set() |
|
|
|
self.jobs = asyncio.Queue() |
|
|
|
self.peers = {} |
|
|
|
|
|
|
|
def start(self): |
|
|
|
'''Prime the event loop with asynchronous servers and jobs.''' |
|
|
|
env = self.env |
|
|
|
loop = self.loop |
|
|
|
|
|
|
|
'''Prime the event loop with asynchronous jobs.''' |
|
|
|
coros = self.block_processor.coros() |
|
|
|
|
|
|
|
if False: |
|
|
|
self.start_servers() |
|
|
|
coros.append(self.reap_jobs()) |
|
|
|
coros.append(self.run_jobs()) |
|
|
|
|
|
|
|
for coro in coros: |
|
|
|
asyncio.ensure_future(coro) |
|
|
|
|
|
|
|
# Signal handlers |
|
|
|
for signame in ('SIGINT', 'SIGTERM'): |
|
|
|
loop.add_signal_handler(getattr(signal, signame), |
|
|
|
partial(self.on_signal, signame)) |
|
|
|
self.loop.add_signal_handler(getattr(signal, signame), |
|
|
|
partial(self.on_signal, signame)) |
|
|
|
|
|
|
|
async def start_servers(self): |
|
|
|
'''Start listening on RPC, TCP and SSL ports. |
|
|
|
|
|
|
|
Does not start a server if the port wasn't specified. Does |
|
|
|
nothing if servers are already running. |
|
|
|
''' |
|
|
|
if self.servers: |
|
|
|
return |
|
|
|
|
|
|
|
env = self.env |
|
|
|
loop = self.loop |
|
|
|
|
|
|
|
def start_servers(self): |
|
|
|
protocol = partial(LocalRPC, self) |
|
|
|
if env.rpc_port is not None: |
|
|
|
host = 'localhost' |
|
|
|
rpc_server = loop.create_server(protocol, host, env.rpc_port) |
|
|
|
self.servers.append(loop.run_until_complete(rpc_server)) |
|
|
|
self.servers.append(await rpc_server) |
|
|
|
self.logger.info('RPC server listening on {}:{:d}' |
|
|
|
.format(host, env.rpc_port)) |
|
|
|
|
|
|
|
protocol = partial(ElectrumX, self, self.daemon, env) |
|
|
|
if env.tcp_port is not None: |
|
|
|
tcp_server = loop.create_server(protocol, env.host, env.tcp_port) |
|
|
|
self.servers.append(loop.run_until_complete(tcp_server)) |
|
|
|
self.servers.append(await tcp_server) |
|
|
|
self.logger.info('TCP server listening on {}:{:d}' |
|
|
|
.format(env.host, env.tcp_port)) |
|
|
|
|
|
|
|
if env.ssl_port is not None: |
|
|
|
ssl_server = loop.create_server(protocol, env.host, env.ssl_port) |
|
|
|
self.servers.append(loop.run_until_complete(ssl_server)) |
|
|
|
# FIXME: update if we want to require Python >= 3.5.3 |
|
|
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) |
|
|
|
ssl_context.load_cert_chain(env.ssl_certfile, |
|
|
|
keyfile=env.ssl_keyfile) |
|
|
|
ssl_server = loop.create_server(protocol, env.host, env.ssl_port, |
|
|
|
ssl=ssl_context) |
|
|
|
self.servers.append(await ssl_server) |
|
|
|
self.logger.info('SSL server listening on {}:{:d}' |
|
|
|
.format(env.host, env.ssl_port)) |
|
|
|
|
|
|
@ -96,30 +108,28 @@ class Controller(LoggedClass): |
|
|
|
task.cancel() |
|
|
|
|
|
|
|
def add_session(self, session): |
|
|
|
'''Add a session representing one incoming connection.''' |
|
|
|
self.sessions.add(session) |
|
|
|
|
|
|
|
def remove_session(self, session): |
|
|
|
'''Remove a session.''' |
|
|
|
self.sessions.remove(session) |
|
|
|
|
|
|
|
def add_job(self, coro): |
|
|
|
'''Queue a job for asynchronous processing.''' |
|
|
|
self.jobs.add(asyncio.ensure_future(coro)) |
|
|
|
self.jobs.put_nowait(coro) |
|
|
|
|
|
|
|
async def reap_jobs(self): |
|
|
|
async def run_jobs(self): |
|
|
|
'''Asynchronously run through the job queue.''' |
|
|
|
while True: |
|
|
|
jobs = set() |
|
|
|
for job in self.jobs: |
|
|
|
if job.done(): |
|
|
|
try: |
|
|
|
job.result() |
|
|
|
except Exception as e: |
|
|
|
traceback.print_exc() |
|
|
|
else: |
|
|
|
jobs.add(job) |
|
|
|
self.logger.info('reaped {:d} jobs, {:d} jobs pending' |
|
|
|
.format(len(self.jobs) - len(jobs), len(jobs))) |
|
|
|
self.jobs = jobs |
|
|
|
await asyncio.sleep(5) |
|
|
|
job = await self.jobs.get() |
|
|
|
try: |
|
|
|
await job |
|
|
|
except asyncio.CancelledError: |
|
|
|
raise |
|
|
|
except Exception: |
|
|
|
# Getting here should probably be considered a bug and fixed |
|
|
|
traceback.print_exc() |
|
|
|
|
|
|
|
def address_status(self, hash168): |
|
|
|
'''Returns status as 32 bytes.''' |
|
|
|