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
This commit is contained in:
Sergi Delgado Segura
2019-11-08 14:16:02 +00:00
parent c08013abd0
commit 20faa04c4c
2 changed files with 83 additions and 61 deletions

View File

@@ -1,6 +1,3 @@
import binascii
from hashlib import sha256
from pisa.logger import Logger from pisa.logger import Logger
from pisa.tools import bitcoin_cli from pisa.tools import bitcoin_cli
from pisa.utils.auth_proxy import JSONRPCException from pisa.utils.auth_proxy import JSONRPCException
@@ -45,6 +42,18 @@ class BlockProcessor:
return block_count 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): def get_missed_blocks(self, last_know_block_hash):
current_block_hash = self.get_best_block_hash() current_block_hash = self.get_best_block_hash()
missed_blocks = [] missed_blocks = []
@@ -72,49 +81,6 @@ class BlockProcessor:
return distance 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. # DISCUSS: This method comes from the Responder and seems like it could go back there.
@staticmethod @staticmethod
# NOTCOVERED # NOTCOVERED

View File

@@ -1,18 +1,22 @@
from uuid import uuid4 from uuid import uuid4
from queue import Queue from queue import Queue
from hashlib import sha256
from threading import Thread 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 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.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from pisa.logger import Logger from pisa.logger import Logger
from pisa.cleaner import Cleaner from pisa.cleaner import Cleaner
from pisa.conf import EXPIRY_DELTA, MAX_APPOINTMENTS, PISA_SECRET_KEY
from pisa.responder import Responder from pisa.responder import Responder
from pisa.appointment import Appointment
from pisa.cryptographer import Cryptographer
from pisa.block_processor import BlockProcessor from pisa.block_processor import BlockProcessor
from pisa.utils.zmq_subscriber import ZMQHandler from pisa.utils.zmq_subscriber import ZMQHandler
from pisa.conf import EXPIRY_DELTA, MAX_APPOINTMENTS, PISA_SECRET_KEY
logger = Logger("Watcher") logger = Logger("Watcher")
@@ -115,31 +119,31 @@ class Watcher:
expired_appointments, self.appointments, self.locator_uuid_map, self.db_manager expired_appointments, self.appointments, self.locator_uuid_map, self.db_manager
) )
potential_matches = BlockProcessor.get_potential_matches(txids, self.locator_uuid_map) matches = self.get_matches(txids, self.locator_uuid_map)
matches = BlockProcessor.get_matches(potential_matches, self.locator_uuid_map, self.appointments) 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 # 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( logger.info(
"Notifying responder and deleting appointment.", "Notifying responder and deleting appointment.",
justice_txid=justice_txid, justice_txid=filtered_match["justice_txid"],
locator=locator, locator=filtered_match["locator"],
uuid=uuid, uuid=uuid,
) )
self.responder.add_response( self.responder.add_response(
uuid, uuid,
dispute_txid, filtered_match["dispute_txid"],
justice_txid, filtered_match["justice_txid"],
justice_rawtx, filtered_match["justice_rawtx"],
self.appointments[uuid].end_time, self.appointments[uuid].end_time,
block_hash, block_hash,
) )
# Delete the appointment and update db # Delete the appointment and update db
Cleaner.delete_complete_appointment( 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 # Register the last processed block for the watcher
@@ -151,3 +155,55 @@ class Watcher:
self.block_queue = Queue() self.block_queue = Queue()
logger.info("No more pending appointments, going back to sleep") 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