From 20faa04c4cbe6623d415c4af22629dfe147624a7 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 8 Nov 2019 14:16:02 +0000 Subject: [PATCH] Refactord Watcher and BlockProcessor. Closes #36. Brings back check_potential_matches and check_matches to the Watcher. The methods have now been renamed to check_matches (old check_potential_matches) and filter_valid_matches (old check_matches) to provide a better description of their purpose. decode_raw_transaction has been added to BlockProcessor --- pisa/block_processor.py | 58 ++++++--------------------- pisa/watcher.py | 86 ++++++++++++++++++++++++++++++++++------- 2 files changed, 83 insertions(+), 61 deletions(-) diff --git a/pisa/block_processor.py b/pisa/block_processor.py index ea3d87d..e3a2bc6 100644 --- a/pisa/block_processor.py +++ b/pisa/block_processor.py @@ -1,6 +1,3 @@ -import binascii -from hashlib import sha256 - from pisa.logger import Logger from pisa.tools import bitcoin_cli from pisa.utils.auth_proxy import JSONRPCException @@ -45,6 +42,18 @@ class BlockProcessor: return block_count + @staticmethod + def decode_raw_transaction(raw_tx): + + try: + tx = bitcoin_cli().decoderawtransaction(raw_tx) + + except JSONRPCException as e: + tx = None + logger.error("Can't build transaction from decoded data.", error=e.error) + + return tx + def get_missed_blocks(self, last_know_block_hash): current_block_hash = self.get_best_block_hash() missed_blocks = [] @@ -72,49 +81,6 @@ class BlockProcessor: return distance - # FIXME: The following two functions does not seem to belong here. They come from the Watcher, and need to be - # separated since they will be reused by the TimeTraveller. - # DISCUSS: 36-who-should-check-appointment-trigger - @staticmethod - def get_potential_matches(txids, locator_uuid_map): - potential_locators = {sha256(binascii.unhexlify(txid)).hexdigest(): txid for txid in txids} - - # Check is any of the tx_ids in the received block is an actual match - intersection = set(locator_uuid_map.keys()).intersection(potential_locators.keys()) - potential_matches = {locator: potential_locators[locator] for locator in intersection} - - if len(potential_matches) > 0: - logger.info("List of potential matches", potential_matches=potential_matches) - - else: - logger.info("No potential matches found") - - return potential_matches - - @staticmethod - # NOTCOVERED - def get_matches(potential_matches, locator_uuid_map, appointments): - matches = [] - - for locator, dispute_txid in potential_matches.items(): - for uuid in locator_uuid_map[locator]: - try: - # ToDo: #20-test-tx-decrypting-edge-cases - justice_rawtx = appointments[uuid].encrypted_blob.decrypt(dispute_txid) - justice_txid = bitcoin_cli().decoderawtransaction(justice_rawtx).get("txid") - logger.info("Match found for locator.", locator=locator, uuid=uuid, justice_txid=justice_txid) - - except JSONRPCException as e: - # Tx decode failed returns error code -22, maybe we should be more strict here. Leaving it simple - # for the POC - justice_txid = None - justice_rawtx = None - logger.error("Can't build transaction from decoded data.", error=e.error) - - matches.append((locator, uuid, dispute_txid, justice_txid, justice_rawtx)) - - return matches - # DISCUSS: This method comes from the Responder and seems like it could go back there. @staticmethod # NOTCOVERED diff --git a/pisa/watcher.py b/pisa/watcher.py index 03da907..f30acd1 100644 --- a/pisa/watcher.py +++ b/pisa/watcher.py @@ -1,18 +1,22 @@ from uuid import uuid4 from queue import Queue +from hashlib import sha256 from threading import Thread +from binascii import unhexlify -from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.serialization import load_pem_private_key +from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.serialization import load_pem_private_key from pisa.logger import Logger from pisa.cleaner import Cleaner -from pisa.conf import EXPIRY_DELTA, MAX_APPOINTMENTS, PISA_SECRET_KEY from pisa.responder import Responder +from pisa.appointment import Appointment +from pisa.cryptographer import Cryptographer from pisa.block_processor import BlockProcessor from pisa.utils.zmq_subscriber import ZMQHandler +from pisa.conf import EXPIRY_DELTA, MAX_APPOINTMENTS, PISA_SECRET_KEY logger = Logger("Watcher") @@ -115,35 +119,35 @@ class Watcher: expired_appointments, self.appointments, self.locator_uuid_map, self.db_manager ) - potential_matches = BlockProcessor.get_potential_matches(txids, self.locator_uuid_map) - matches = BlockProcessor.get_matches(potential_matches, self.locator_uuid_map, self.appointments) + matches = self.get_matches(txids, self.locator_uuid_map) + filtered_matches = self.filter_valid_matches(matches, self.locator_uuid_map, self.appointments) - for locator, uuid, dispute_txid, justice_txid, justice_rawtx in matches: + for uuid, filtered_match in filtered_matches.items(): # Errors decrypting the Blob will result in a None justice_txid - if justice_txid is not None: + if filtered_match["valid_match"] is True: logger.info( "Notifying responder and deleting appointment.", - justice_txid=justice_txid, - locator=locator, + justice_txid=filtered_match["justice_txid"], + locator=filtered_match["locator"], uuid=uuid, ) self.responder.add_response( uuid, - dispute_txid, - justice_txid, - justice_rawtx, + filtered_match["dispute_txid"], + filtered_match["justice_txid"], + filtered_match["justice_rawtx"], self.appointments[uuid].end_time, block_hash, ) # Delete the appointment and update db Cleaner.delete_complete_appointment( - self.appointments, self.locator_uuid_map, locator, uuid, self.db_manager + self.appointments, self.locator_uuid_map, filtered_match["locator"], uuid, self.db_manager ) - # Register the last processed block for the watcher - self.db_manager.store_last_block_hash_watcher(block_hash) + # Register the last processed block for the watcher + self.db_manager.store_last_block_hash_watcher(block_hash) # Go back to sleep if there are no more appointments self.asleep = True @@ -151,3 +155,55 @@ class Watcher: self.block_queue = Queue() logger.info("No more pending appointments, going back to sleep") + + @staticmethod + def compute_locator(tx_id): + return sha256(unhexlify(tx_id)).hexdigest() + + # DISCUSS: 36-who-should-check-appointment-trigger + @staticmethod + def get_matches(txids, locator_uuid_map): + potential_locators = {Watcher.compute_locator(txid): txid for txid in txids} + + # Check is any of the tx_ids in the received block is an actual match + intersection = set(locator_uuid_map.keys()).intersection(potential_locators.keys()) + matches = {locator: potential_locators[locator] for locator in intersection} + + if len(matches) > 0: + logger.info("List of matches", potential_matches=matches) + + else: + logger.info("No matches found") + + return matches + + @staticmethod + # NOTCOVERED + def filter_valid_matches(matches, locator_uuid_map, appointments): + filtered_matches = {} + + for locator, dispute_txid in matches.items(): + for uuid in locator_uuid_map[locator]: + + justice_rawtx = Cryptographer.decrypt(appointments[uuid].encrypted_blob, dispute_txid) + justice_tx = BlockProcessor.decode_raw_transaction(justice_rawtx) + + if justice_rawtx is not None: + justice_txid = justice_tx.get("txid") + valid_match = True + + logger.info("Match found for locator.", locator=locator, uuid=uuid, justice_txid=justice_txid) + + else: + justice_txid = None + valid_match = False + + filtered_matches[uuid] = { + "locator": locator, + "dispute_txid": dispute_txid, + "justice_txid": justice_txid, + "justice_rawtx": justice_rawtx, + "valid_match": valid_match, + } + + return filtered_matches