Ejemplo n.º 1
0
    def __init__(self, receiving_queues, block_processor,
                 bitcoind_feed_params):
        self.logger = get_logger(component=ChainMonitor.__name__)
        self.last_tips = []

        self.check_tip = Event()
        self.lock = Condition()

        self.zmqContext = zmq.Context()
        self.zmqSubSocket = self.zmqContext.socket(zmq.SUB)
        self.zmqSubSocket.setsockopt(zmq.RCVHWM, 0)
        self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "hashblock")
        self.zmqSubSocket.connect("%s://%s:%s" % (
            bitcoind_feed_params.get("BTC_FEED_PROTOCOL"),
            bitcoind_feed_params.get("BTC_FEED_CONNECT"),
            bitcoind_feed_params.get("BTC_FEED_PORT"),
        ))

        self.receiving_queues = receiving_queues

        self.polling_delta = 60
        self.max_block_window_size = 10
        self.block_processor = block_processor
        self.queue = Queue()
        self.status = ChainMonitorStatus.IDLE
Ejemplo n.º 2
0
 def __init__(self, watcher, internal_api_endpoint, max_workers,
              stop_command_event):
     self.logger = get_logger(component=InternalAPI.__name__)
     self.watcher = watcher
     self.endpoint = internal_api_endpoint
     self.stop_command_event = stop_command_event
     self.rpc_server = grpc.server(
         futures.ThreadPoolExecutor(max_workers=max_workers))
     self.rpc_server.add_insecure_port(self.endpoint)
     add_TowerServicesServicer_to_server(
         _InternalAPI(watcher, stop_command_event, self.logger),
         self.rpc_server)
Ejemplo n.º 3
0
 def __init__(self, db_manager, gatekeeper, carrier, block_processor):
     self.logger = get_logger(component=Responder.__name__)
     self.trackers = dict()
     self.tx_tracker_map = dict()
     self.unconfirmed_txs = []
     self.missed_confirmations = dict()
     self.block_queue = Queue()
     self.db_manager = db_manager
     self.gatekeeper = gatekeeper
     self.carrier = carrier
     self.block_processor = block_processor
     self.last_known_block = db_manager.load_last_block_hash_responder()
     self.rw_lock = rwlock.RWLockWrite()
Ejemplo n.º 4
0
    def __init__(self, db_path):
        if not isinstance(db_path, str):
            raise ValueError("db_path must be a valid path/name")

        self.logger = get_logger(component=AppointmentsDBM.__name__)

        try:
            super().__init__(db_path)

        except plyvel.Error as e:
            if "LOCK: Resource temporarily unavailable" in str(e):
                self.logger.info(
                    "The db is already being used by another process (LOCK)")

            raise e
Ejemplo n.º 5
0
def main(config):
    setup_data_folder(config.get("DATA_DIR"))

    logging_server_ready = multiprocessing.Event()
    stop_logging_server = multiprocessing.Event()
    logging_port = multiprocessing.Value("i")
    logging_process = multiprocessing.Process(
        target=serve_logging,
        daemon=True,
        args=(config.get("LOG_FILE"), logging_port, config.get("DAEMON"),
              logging_server_ready, stop_logging_server),
    )
    logging_process.start()
    logging_server_ready.wait()

    setup_logging(logging_port.value)
    logger = get_logger(component="Daemon")

    if not os.path.exists(
            config.get("TEOS_SECRET_KEY")) or config.get("OVERWRITE_KEY"):
        logger.info("Generating a new key pair")
        sk = Cryptographer.generate_key()
        Cryptographer.save_key_file(sk.to_der(), "teos_sk",
                                    config.get("DATA_DIR"))

    else:
        logger.info("Tower identity found. Loading keys")
        secret_key_der = Cryptographer.load_key_file(
            config.get("TEOS_SECRET_KEY"))

        if not secret_key_der:
            raise IOError("TEOS private key cannot be loaded")
        sk = Cryptographer.load_private_key_der(secret_key_der)

    # Windows cannot run gunicorn
    if os.name == "nt" and config.get("WSGI") == "gunicorn":
        logger.warning(
            "Windows cannot run gunicorn as WSGI. Changing to waitress")
        config["WSGI"] = "waitress"

    try:
        TeosDaemon(config, sk, logger, logging_port.value, stop_logging_server,
                   logging_process).start()
    except Exception as e:
        logger.error("An error occurred: {}. Shutting down".format(e))
        stop_logging_server.set()
        logging_process.join()
        exit(1)
Ejemplo n.º 6
0
    def __init__(self, db_manager, gatekeeper, block_processor, responder, sk,
                 max_appointments, blocks_in_cache):
        self.logger = get_logger(component=Watcher.__name__)

        self.appointments = dict()
        self.locator_uuid_map = dict()
        self.block_queue = Queue()
        self.db_manager = db_manager
        self.gatekeeper = gatekeeper
        self.block_processor = block_processor
        self.responder = responder
        self.max_appointments = max_appointments
        self.signing_key = sk
        self.last_known_block = db_manager.load_last_block_hash_watcher()
        self.locator_cache = LocatorCache(blocks_in_cache)
        self.rw_lock = rwlock.RWLockWrite()
Ejemplo n.º 7
0
    def __init__(self, inspector, internal_api_endpoint):

        self.logger = get_logger(component=API.__name__)
        self.app = Flask(__name__)
        self.inspector = inspector
        self.internal_api_endpoint = internal_api_endpoint
        channel = grpc.insecure_channel(internal_api_endpoint)
        self.stub = TowerServicesStub(channel)

        # Adds all the routes to the functions listed above.
        routes = {
            "/register": (self.register, ["POST"]),
            "/add_appointment": (self.add_appointment, ["POST"]),
            "/get_appointment": (self.get_appointment, ["POST"]),
            "/get_subscription_info": (self.get_subscription_info, ["POST"]),
        }

        for url, params in routes.items():
            self.app.add_url_rule(url, view_func=params[0], methods=params[1])
Ejemplo n.º 8
0
 def __init__(self, blocks_in_cache):
     self.logger = get_logger(component=LocatorCache.__name__)
     self.cache = dict()
     self.blocks = OrderedDict()
     self.cache_size = blocks_in_cache
     self.rw_lock = rwlock.RWLockWrite()
Ejemplo n.º 9
0
 def __init__(self, btc_connect_params):
     self.logger = get_logger(component=BlockProcessor.__name__)
     self.btc_connect_params = btc_connect_params
Ejemplo n.º 10
0
 def __init__(self, btc_connect_params, bitcoind_reachable):
     self.logger = get_logger(component=BlockProcessor.__name__)
     self.btc_connect_params = btc_connect_params
     self.bitcoind_reachable = bitcoind_reachable
Ejemplo n.º 11
0
def test_get_logger():
    # Test that get_logger actually adds a field called "component" with the expected value.
    # As the public interface of the class does not expose the initial_values, we rely on the output
    # of `repr` to check if the expected fields are indeed present.
    logger = get_logger("MyAwesomeComponent")
    assert "'component': 'MyAwesomeComponent'" in repr(logger)
Ejemplo n.º 12
0
 def __init__(self, btc_connect_params, bitcoind_reachable):
     self.logger = get_logger(component=Carrier.__name__)
     self.btc_connect_params = btc_connect_params
     self.bitcoind_reachable = bitcoind_reachable
     self.issued_receipts = {}
Ejemplo n.º 13
0
class Cleaner:
    """
    The :class:`Cleaner` is in charge of removing outdated/completed data from the tower.

    Mutable objects (like dicts) are passed-by-reference in Python, so no return is needed for the Cleaner.
    """

    logger = get_logger(component="Cleaner")

    @staticmethod
    def delete_appointment_from_memory(uuid, appointments, locator_uuid_map):
        """
        Deletes an appointment from memory (``appointments`` and ``locator_uuid_map`` dictionaries). If the given
        appointment does not share locator with any other, the map will completely removed, otherwise, the uuid will be
        removed from the map.

        Args:
            uuid (:obj:`str`): the identifier of the appointment to be deleted.
            appointments (:obj:`dict`): the appointments dictionary from where the appointment should be removed.
            locator_uuid_map (:obj:`dict`): the ``locator:uuid`` map from where the appointment should also be removed.
        """

        locator = appointments[uuid].get("locator")

        # Delete the appointment
        appointments.pop(uuid)

        # If there was only one appointment that matches the locator we can delete the whole list
        if len(locator_uuid_map[locator]) == 1:
            locator_uuid_map.pop(locator)
        else:
            # Otherwise we just delete the appointment that matches locator:appointment_pos
            locator_uuid_map[locator].remove(uuid)

    @staticmethod
    def delete_appointment_from_db(uuid, db_manager):
        """
        Deletes an appointment from the appointments database.

        Args:
            uuid (:obj:`str`): the identifier of the appointment to be deleted.
            db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): an instance of the appointment
                database manager to interact with the database.
        """

        db_manager.delete_watcher_appointment(uuid)
        db_manager.delete_triggered_appointment_flag(uuid)

    @staticmethod
    def delete_appointments(outdated_appointments,
                            appointments,
                            locator_uuid_map,
                            db_manager,
                            outdated=False):
        """
        Deletes appointments that have completed (with no trigger), or outdated, both from memory
        (:obj:`Watcher <teos.watcher.Watcher>`) and disk.

        Currently, an appointment is only completed if it cannot make it to the
        (:obj:`Responder <teos.responder.Responder>`), otherwise, it will be flagged as triggered and removed once the
        tracker is completed.

        Args:
            outdated_appointments (:obj:`list`): a list of appointments to be deleted.
            appointments (:obj:`dict`): a dictionary containing all the :mod:`Watcher <teos.watcher.Watcher>`
                appointments.
            locator_uuid_map (:obj:`dict`): a ``locator:uuid`` map for the :obj:`Watcher <teos.watcher.Watcher>`
                appointments.
            db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): an instance of the appointment
                database manager to interact with the database.
            outdated (:obj:`bool`): whether the appointments have been outdated or completed.
        """

        for uuid in outdated_appointments:
            locator = appointments[uuid].get("locator")
            if outdated:
                Cleaner.logger.info(
                    "End time reached with no breach. Deleting appointment",
                    locator=locator,
                    uuid=uuid)
            else:
                Cleaner.logger.warning(
                    "Appointment cannot be completed, it contains invalid data. Deleting",
                    locator=locator,
                    uuid=uuid)

            Cleaner.delete_appointment_from_memory(uuid, appointments,
                                                   locator_uuid_map)

        # Outdated appointments are not flagged, so they can be deleted without caring about the db flag.
        db_manager.batch_delete_watcher_appointments(outdated_appointments)

    @staticmethod
    def flag_triggered_appointments(triggered_appointments, appointments,
                                    locator_uuid_map, db_manager):
        """
        Deletes a list of triggered appointment from memory (:obj:`Watcher <teos.watcher.Watcher>`) and flags them as
        triggered on disk.

        Args:
            triggered_appointments (:obj:`list`): a list of appointments to be flagged as triggered on the database.
            appointments (:obj:`dict`): a dictionary containing all the :obj:`Watcher <teos.watcher.Watcher>`
                appointments.
            locator_uuid_map (:obj:`dict`): a ``locator:uuid`` map for the :obj:`Watcher <teos.watcher.Watcher>`
                appointments.
            db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): an instance of the appointment
                database manager to interact with the database.
        """

        for uuid in triggered_appointments:
            Cleaner.delete_appointment_from_memory(uuid, appointments,
                                                   locator_uuid_map)
            db_manager.create_triggered_appointment_flag(uuid)

    @staticmethod
    def delete_trackers(completed_trackers,
                        height,
                        trackers,
                        tx_tracker_map,
                        db_manager,
                        outdated=False):
        """
        Deletes completed/outdated trackers both from memory (:obj:`Responder <teos.responder.Responder>`) and disk
        (from the :obj:`Responder`'s and :obj:`Watcher`'s databases).

        Args:
            completed_trackers (:obj:`dict`): a dict of completed/outdated trackers to be deleted
                (``uuid:confirmations``).
            trackers (:obj:`dict`): a dictionary containing all the :obj:`Responder <teos.responder.Responder>`
                trackers.
            height (:obj:`int`): the block height at which the trackers were completed.
            tx_tracker_map (:obj:`dict`): a ``penalty_txid:uuid`` map for the :obj:`Responder
                <teos.responder.Responder>` trackers.
            db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): an instance of the appointment
                database manager to interact with the database.
            outdated (:obj:`bool`): whether the trackers have been outdated or not. Defaults to False.
        """

        for uuid in completed_trackers:

            if outdated:
                Cleaner.logger.info(
                    "Appointment couldn't be completed. Expiry reached but penalty didn't make it to the chain",
                    uuid=uuid,
                    height=height,
                )
            else:
                Cleaner.logger.info(
                    "Appointment completed. Penalty transaction was irrevocably confirmed",
                    uuid=uuid,
                    height=height)

            penalty_txid = trackers[uuid].get("penalty_txid")
            trackers.pop(uuid)

            if len(tx_tracker_map[penalty_txid]) == 1:
                tx_tracker_map.pop(penalty_txid)

                Cleaner.logger.info("No more trackers for penalty transaction",
                                    penalty_txid=penalty_txid)

            else:
                tx_tracker_map[penalty_txid].remove(uuid)

        # Delete appointment from the db (from watcher's and responder's db) and remove flag
        db_manager.batch_delete_responder_trackers(completed_trackers)
        db_manager.batch_delete_watcher_appointments(completed_trackers)
        db_manager.batch_delete_triggered_appointment_flag(completed_trackers)

    @staticmethod
    def delete_gatekeeper_appointments(appointment_to_delete, registered_users,
                                       user_db):
        """
        Deletes a list of outdated / completed appointments of a given user both from memory and the UserDB.

        Args:
            appointment_to_delete (:obj:`dict`): ``uuid:user_id`` dict containing the appointments to delete
                (outdated + completed)
            registered_users (:obj:`dict`): a dictionary of registered users from the gatekeeper.
            user_db (:obj:`UsersDBM <teos.user_dbm.UsersDBM>`): A user database manager instance to interact with the
            database.
        """

        user_ids = []
        # Remove appointments from memory
        for uuid, user_id in appointment_to_delete.items():
            if user_id in registered_users and uuid in registered_users[
                    user_id].appointments:
                # Remove the appointment from the appointment list and update the available slots
                freed_slots = registered_users[user_id].appointments.pop(uuid)
                registered_users[user_id].available_slots += freed_slots

                if user_id not in user_ids:
                    user_ids.append(user_id)

        # Store the updated users in the DB
        for user_id in user_ids:
            user_db.store_user(user_id, registered_users[user_id].to_dict())

    @staticmethod
    def delete_outdated_users(outdated_users, registered_users, user_db):
        """
        Deletes users whose subscription has been outdated, alongside all their associated data (appointments and
        trackers).

        Args:
            outdated_users (:obj:`list): a list of user_ids to be deleted.
            registered_users (:obj:`dict`): a dictionary of registered users from the gatekeeper.
            user_db (:obj:`UsersDBM <teos.user_dbm.UsersDBM>`): A user database manager instance to interact with the
            database.
        """

        for user_id in outdated_users:
            registered_users.pop(user_id)
            user_db.delete_user(user_id)
Ejemplo n.º 14
0
 def __init__(self, btc_connect_params):
     self.logger = get_logger(component=Carrier.__name__)
     self.btc_connect_params = btc_connect_params
     self.issued_receipts = {}