def test_delete_outdated_appointments(db_manager):
    for _ in range(ITERATIONS):
        appointments, locator_uuid_map = set_up_appointments(
            db_manager, MAX_ITEMS)
        outdated_appointments = random.sample(list(appointments.keys()),
                                              k=ITEMS)

        Cleaner.delete_outdated_appointments(outdated_appointments,
                                             appointments, locator_uuid_map,
                                             db_manager)

        assert not set(outdated_appointments).issubset(appointments.keys())
Exemple #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()

            # When the ChainMonitor is stopped, a final ChainMonitor.END_MESSAGE message is sent
            if block_hash == ChainMonitor.END_MESSAGE:
                break

            block = self.block_processor.get_block(block_hash)
            self.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)

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

                    Cleaner.delete_outdated_appointments(
                        outdated_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():
                        self.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)
                    appointments_to_delete_gatekeeper = {
                        uuid: self.appointments[uuid].get("user_id")
                        for uuid in appointments_to_delete
                    }
                    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)
                    # Remove invalid appointments from the Gatekeeper
                    self.gatekeeper.delete_appointments(
                        appointments_to_delete_gatekeeper)

                    if not self.appointments:
                        self.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()