Esempio n. 1
0
def test_delete_expired_appointment(db_manager):
    for _ in range(ITERATIONS):
        appointments, locator_uuid_map = set_up_appointments(
            db_manager, MAX_ITEMS)
        expired_appointments = random.sample(list(appointments.keys()),
                                             k=ITEMS)

        Cleaner.delete_expired_appointments(expired_appointments, appointments,
                                            locator_uuid_map, db_manager)

        assert not set(expired_appointments).issubset(appointments.keys())
Esempio n. 2
0
    def do_watch(self):
        """
        Monitors the blockchain for channel breaches.

        This is the main method of the :obj:`Watcher` and the one in charge to pass appointments to the
        :obj:`Responder <teos.responder.Responder>` upon detecting a breach.
        """

        # Distinguish fresh bootstraps from bootstraps from db
        if self.last_known_block is None:
            self.last_known_block = self.block_processor.get_best_block_hash()
            self.db_manager.store_last_block_hash_watcher(
                self.last_known_block)

        # Initialise the locator cache with the last ``cache_size`` blocks.
        self.locator_cache.init(self.last_known_block, self.block_processor)

        while True:
            block_hash = self.block_queue.get()
            block = self.block_processor.get_block(block_hash)
            logger.info("New block received",
                        block_hash=block_hash,
                        prev_block_hash=block.get("previousblockhash"))

            # If a reorg is detected, the cache is fixed to cover the last `cache_size` blocks of the new chain
            if self.last_known_block != block.get("previousblockhash"):
                self.locator_cache.fix(block_hash, self.block_processor)

            txids = block.get("tx")
            # Compute the locator for every transaction in the block and add them to the cache
            locator_txid_map = {compute_locator(txid): txid for txid in txids}
            self.locator_cache.update(block_hash, locator_txid_map)

            if len(self.appointments) > 0 and locator_txid_map:
                expired_appointments = self.gatekeeper.get_expired_appointments(
                    block["height"])
                # Make sure we only try to delete what is on the Watcher (some appointments may have been triggered)
                expired_appointments = list(
                    set(expired_appointments).intersection(
                        self.appointments.keys()))

                # Keep track of the expired appointments before deleting them from memory
                appointments_to_delete_gatekeeper = {
                    uuid: self.appointments[uuid].get("user_id")
                    for uuid in expired_appointments
                }

                Cleaner.delete_expired_appointments(expired_appointments,
                                                    self.appointments,
                                                    self.locator_uuid_map,
                                                    self.db_manager)

                valid_breaches, invalid_breaches = self.filter_breaches(
                    self.get_breaches(locator_txid_map))

                triggered_flags = []
                appointments_to_delete = []

                for uuid, breach in valid_breaches.items():
                    logger.info(
                        "Notifying responder and deleting appointment",
                        penalty_txid=breach["penalty_txid"],
                        locator=breach["locator"],
                        uuid=uuid,
                    )

                    receipt = self.responder.handle_breach(
                        uuid,
                        breach["locator"],
                        breach["dispute_txid"],
                        breach["penalty_txid"],
                        breach["penalty_rawtx"],
                        self.appointments[uuid].get("user_id"),
                        block_hash,
                    )

                    # FIXME: Only necessary because of the triggered appointment approach. Fix if it changes.

                    if receipt.delivered:
                        Cleaner.delete_appointment_from_memory(
                            uuid, self.appointments, self.locator_uuid_map)
                        triggered_flags.append(uuid)
                    else:
                        appointments_to_delete.append(uuid)

                # Appointments are only flagged as triggered if they are delivered, otherwise they are just deleted.
                appointments_to_delete.extend(invalid_breaches)
                self.db_manager.batch_create_triggered_appointment_flag(
                    triggered_flags)

                # Update the dictionary with the completed appointments
                appointments_to_delete_gatekeeper.update({
                    uuid: self.appointments[uuid].get("user_id")
                    for uuid in appointments_to_delete
                })

                Cleaner.delete_completed_appointments(appointments_to_delete,
                                                      self.appointments,
                                                      self.locator_uuid_map,
                                                      self.db_manager)

                # Remove expired and completed appointments from the Gatekeeper
                Cleaner.delete_gatekeeper_appointments(
                    self.gatekeeper, appointments_to_delete_gatekeeper)

                if len(self.appointments) != 0:
                    logger.info("No more pending appointments")

            # Register the last processed block for the Watcher
            self.db_manager.store_last_block_hash_watcher(block_hash)
            self.last_known_block = block.get("hash")
            self.block_queue.task_done()
Esempio n. 3
0
    def do_watch(self):
        """
        Monitors the blockchain whilst there are pending appointments.

        This is the main method of the :obj:`Watcher` and the one in charge to pass appointments to the
        :obj:`Responder <teos.responder.Responder>` upon detecting a breach.
        """

        while True:
            block_hash = self.block_queue.get()
            block = self.block_processor.get_block(block_hash)
            logger.info("New block received", block_hash=block_hash, prev_block_hash=block.get("previousblockhash"))

            if len(self.appointments) > 0 and block is not None:
                txids = block.get("tx")

                expired_appointments = [
                    uuid
                    for uuid, appointment_data in self.appointments.items()
                    if block["height"] > appointment_data.get("end_time") + self.expiry_delta
                ]

                Cleaner.delete_expired_appointments(
                    expired_appointments, self.appointments, self.locator_uuid_map, self.db_manager
                )

                valid_breaches, invalid_breaches = self.filter_valid_breaches(self.get_breaches(txids))

                triggered_flags = []
                appointments_to_delete = []

                for uuid, breach in valid_breaches.items():
                    logger.info(
                        "Notifying responder and deleting appointment",
                        penalty_txid=breach["penalty_txid"],
                        locator=breach["locator"],
                        uuid=uuid,
                    )

                    receipt = self.responder.handle_breach(
                        uuid,
                        breach["locator"],
                        breach["dispute_txid"],
                        breach["penalty_txid"],
                        breach["penalty_rawtx"],
                        self.appointments[uuid].get("end_time"),
                        block_hash,
                    )

                    # FIXME: Only necessary because of the triggered appointment approach. Fix if it changes.

                    if receipt.delivered:
                        Cleaner.delete_appointment_from_memory(uuid, self.appointments, self.locator_uuid_map)
                        triggered_flags.append(uuid)
                    else:
                        appointments_to_delete.append(uuid)

                # Appointments are only flagged as triggered if they are delivered, otherwise they are just deleted.
                appointments_to_delete.extend(invalid_breaches)
                self.db_manager.batch_create_triggered_appointment_flag(triggered_flags)

                Cleaner.delete_completed_appointments(
                    appointments_to_delete, self.appointments, self.locator_uuid_map, self.db_manager
                )

                if len(self.appointments) is 0:
                    logger.info("No more pending appointments")

            # Register the last processed block for the watcher
            self.db_manager.store_last_block_hash_watcher(block_hash)
            self.block_queue.task_done()