From 6b025a2d9cd0797eeb4c4c069afee0c4784092af Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 9 Apr 2020 17:58:09 +0200 Subject: [PATCH] Improves add_appointment method. Has some pending fixmes - Start and end time have to be dealt with (changes required on the tower side) - Sends appointments to every register tower. We may want to manage this better --- watchtower-plugin/tower_info.py | 14 ++++++--- watchtower-plugin/watchtower.py | 55 ++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/watchtower-plugin/tower_info.py b/watchtower-plugin/tower_info.py index 4b2034b..ce50d96 100644 --- a/watchtower-plugin/tower_info.py +++ b/watchtower-plugin/tower_info.py @@ -1,19 +1,23 @@ class TowerInfo: - def __init__(self, endpoint, available_slots): + def __init__(self, endpoint, available_slots, appointments=None): self.endpoint = endpoint self.available_slots = available_slots + if not appointments: + self.appointments = {} + else: + self.appointments = appointments + @classmethod def from_dict(cls, tower_data): endpoint = tower_data.get("endpoint") available_slots = tower_data.get("available_slots") + appointments = tower_data.get("appointments") - if any(v is None for v in [endpoint, available_slots]): + if any(v is None for v in [endpoint, available_slots, appointments]): raise ValueError("Wrong appointment data, some fields are missing") - if available_slots is None: - raise ValueError("Wrong tower data, some fields are missing") - return cls(endpoint, available_slots) + return cls(endpoint, available_slots, appointments) def to_dict(self): return self.__dict__ diff --git a/watchtower-plugin/watchtower.py b/watchtower-plugin/watchtower.py index 624c842..6e2e3a0 100755 --- a/watchtower-plugin/watchtower.py +++ b/watchtower-plugin/watchtower.py @@ -3,15 +3,18 @@ import os import plyvel from pyln.client import Plugin +from common.tools import compute_locator +from common.appointment import Appointment from common.config_loader import ConfigLoader from common.cryptographer import Cryptographer +from common.exceptions import InvalidParameter, SignatureError, EncryptionError import arg_parser from tower_info import TowerInfo from towers_dbm import TowersDBM from keys import generate_keys, load_keys from net.http import post_request, process_post_response -from exceptions import TowerConnectionError, TowerResponseError, InvalidParameter +from exceptions import TowerConnectionError, TowerResponseError DATA_DIR = os.path.expanduser("~/.watchtower/") @@ -123,7 +126,7 @@ def get_appointment(plugin, *args): :obj:`dict`: a dictionary containing the appointment data. """ - # FIXME: All responses from the tower should be signed. Not using teos_pk atm. + # FIXME: All responses from the tower should be signed. try: tower_id, locator = arg_parser.parse_get_appointment_arguments(args) @@ -149,9 +152,51 @@ def get_appointment(plugin, *args): @plugin.hook("commitment_revocation") def add_appointment(plugin, **kwargs): - commitment_txid = kwargs.get("commitment_txid") - penalty_tx = kwargs.get("penalty_tx") - plugin.log("commitment_txid {}, penalty_tx: {}".format(commitment_txid, penalty_tx)) + try: + # FIXME: start_time and end_time are temporary. Fix it on the tower side and remove it from there + block_height = plugin.rpc.getchaininfo().get("blockcount") + start_time = block_height + 1 + end_time = block_height + 10 + + commitment_txid, penalty_tx = arg_parser.parse_add_appointment_arguments(kwargs) + appointment = Appointment( + locator=compute_locator(commitment_txid), + start_time=start_time, + end_time=end_time, + to_self_delay=20, + encrypted_blob=Cryptographer.encrypt(penalty_tx, commitment_txid), + ) + + signature = Cryptographer.sign(appointment.serialize(), plugin.wt_client.sk) + data = {"appointment": appointment.to_dict(), "signature": signature} + + # Send appointment to the server. + # FIXME: sending the appointment to all registered towers atm. Some management would be nice. + for tower_id, tower in plugin.wt_client.towers.items(): + plugin.log("Sending appointment to the Eye of Satoshi at {}".format(tower.endpoint)) + add_appointment_endpoint = "{}/add_appointment".format(tower.endpoint) + response = process_post_response(post_request(data, add_appointment_endpoint)) + + signature = response.get("signature") + # Check that the server signed the appointment as it should. + if not signature: + raise TowerResponseError("The response does not contain the signature of the appointment") + + rpk = Cryptographer.recover_pk(appointment.serialize(), signature) + if not tower_id != Cryptographer.get_compressed_pk(rpk): + raise TowerResponseError("The returned appointment's signature is invalid") + + plugin.log("Appointment accepted and signed by the Eye of Satoshi at {}".format(tower.endpoint)) + plugin.log("Remaining slots: {}".format(response.get("available_slots"))) + + # TODO: Not storing the whole appointments for now. The node should be able to recreate all the required + # data if needed. + plugin.wt_client.towers[tower_id].appointments[appointment.locator] = signature + plugin.wt_client.db_manager.store_tower_record(tower_id, plugin.wt_client.towers[tower_id]) + + except (InvalidParameter, EncryptionError, SignatureError, TowerResponseError) as e: + plugin.log(str(e), level="error") + return {"result": "continue"}