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