obelisk

Electrum server using libbitcoin as its backend
git clone https://git.parazyd.org/obelisk
Log | Files | Refs | README | LICENSE

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())