obelisk.py (3574B)
1 #!/usr/bin/env python3 2 # Copyright (C) 2020-2021 Ivan J. <parazyd@dyne.org> 3 # 4 # This file is part of obelisk 5 # 6 # This program is free software: you can redistribute it and/or modify 7 # it under the terms of the GNU Affero General Public License version 3 8 # as published by the Free Software Foundation. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Affero General Public License for more details. 14 # 15 # You should have received a copy of the GNU Affero General Public License 16 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 import asyncio 18 import sys 19 from argparse import ArgumentParser 20 from configparser import RawConfigParser, NoSectionError 21 from logging import getLogger, FileHandler, Formatter, StreamHandler 22 from os import devnull 23 24 from electrumobelisk.protocol import ElectrumProtocol, VERSION 25 26 # Used for destructor/cleanup 27 PROTOCOL = None 28 29 30 def logger_config(log, config): 31 """Setup logging""" 32 fmt = Formatter("%(asctime)s\t%(levelname)s\t%(message)s") 33 logstream = StreamHandler() 34 logstream.setFormatter(fmt) 35 debuglevel = config.get("obelisk", "log_level", fallback="INFO") 36 logstream.setLevel(debuglevel) 37 log.addHandler(logstream) 38 filename = config.get("obelisk", "log_file", fallback=devnull) 39 append_log = config.getboolean("obelisk", "append_log", fallback=False) 40 logfile = FileHandler(filename, mode=("a" if append_log else "w")) 41 logfile.setFormatter(fmt) 42 logfile.setLevel(debuglevel) 43 log.addHandler(logfile) 44 log.setLevel(debuglevel) 45 return log, filename 46 47 48 async def run_electrum_server(config, chain): 49 """Server coroutine""" 50 log = getLogger("obelisk") 51 host = config.get("obelisk", "host") 52 port = int(config.get("obelisk", "port")) 53 54 endpoints = {} 55 endpoints["query"] = config.get("obelisk", "query") 56 endpoints["heart"] = config.get("obelisk", "heart") 57 endpoints["block"] = config.get("obelisk", "block") 58 endpoints["trans"] = config.get("obelisk", "trans") 59 60 server_cfg = {} 61 server_cfg["server_hostname"] = "localhost" # TODO: <- should be public? 62 server_cfg["server_port"] = port 63 64 global PROTOCOL 65 PROTOCOL = ElectrumProtocol(log, chain, endpoints, server_cfg) 66 67 server = await asyncio.start_server(PROTOCOL.recv, host, port) 68 async with server: 69 await server.serve_forever() 70 71 72 def main(): 73 """Main orchestration""" 74 parser = ArgumentParser(description=f"obelisk {VERSION}") 75 parser.add_argument("config_file", help="Path to config file") 76 args = parser.parse_args() 77 78 try: 79 config = RawConfigParser() 80 config.read(args.config_file) 81 config.options("obelisk") 82 except NoSectionError: 83 print(f"error: Invalid config file {args.config_file}") 84 return 1 85 86 log = getLogger("obelisk") 87 log, logfilename = logger_config(log, config) 88 log.info(f"Starting obelisk {VERSION}") 89 log.info(f"Logging to {logfilename}") 90 91 chain = config.get("obelisk", "chain") 92 if chain not in ("mainnet", "testnet"): 93 log.error("chain is not 'mainnet' or 'testnet'") 94 return 1 95 96 try: 97 asyncio.run(run_electrum_server(config, chain)) 98 except KeyboardInterrupt: 99 print("\r", end="") 100 log.debug("Caught KeyboardInterrupt, exiting...") 101 if PROTOCOL: 102 asyncio.run(PROTOCOL.stop()) 103 return 0 104 105 return 1 106 107 108 if __name__ == "__main__": 109 sys.exit(main())