From f5c634ed140c7107f3e28772097919a28094ee50 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 5 May 2020 17:03:31 +0200 Subject: [PATCH] plugin - repurpose invalid_appointments and adds misbehaving_proof invalid_appointments in TowerInfo was used to stored invalid appointment responses from the tower when it was misbehaving (i.e. wrong or missing signature). However, a single item was stored since the tower is abandoned after misbehaving. data reported as invalid by the tower was not backed up. invalid_appointments now stores the appointment rejected by the tower and reported as invalid. misbehaving_proof stores the proof of misbehaviour by the tower (single item) --- watchtower-plugin/net/http.py | 11 ++++++---- watchtower-plugin/retrier.py | 9 +++++++- watchtower-plugin/tower_info.py | 38 +++++++++++---------------------- watchtower-plugin/watchtower.py | 12 ++++++++++- 4 files changed, 39 insertions(+), 31 deletions(-) diff --git a/watchtower-plugin/net/http.py b/watchtower-plugin/net/http.py index 97e47d3..ea31833 100644 --- a/watchtower-plugin/net/http.py +++ b/watchtower-plugin/net/http.py @@ -42,9 +42,8 @@ def add_appointment(plugin, tower_id, tower, appointment_dict, signature): raise TowerResponseError(message, status="subscription error") elif data.get("error_code") >= errors.INVALID_REQUEST_FORMAT: - # DISCUSS: It may be worth backing up the data since otherwise the update is dropped message = f"Appointment sent to {tower_id} is invalid" - raise TowerResponseError(message, status="reachable") + raise TowerResponseError(message, status="reachable", invalid_appointment=True) elif status_code == constants.HTTP_SERVICE_UNAVAILABLE: # Flag appointment for retry @@ -68,9 +67,13 @@ def send_appointment(tower_id, tower, appointment_dict, signature): raise SignatureError("The response does not contain the signature of the appointment", signature=None) rpk = Cryptographer.recover_pk(Appointment.from_dict(appointment_dict).serialize(), tower_signature) - if tower_id != Cryptographer.get_compressed_pk(rpk): + recovered_id = Cryptographer.get_compressed_pk(rpk) + if tower_id != recovered_id: raise SignatureError( - "The returned appointment's signature is invalid", tower_id=tower_id, rpk=rpk, signature=tower_signature + "The returned appointment's signature is invalid", + tower_id=tower_id, + recovered_id=recovered_id, + signature=tower_signature, ) return response diff --git a/watchtower-plugin/retrier.py b/watchtower-plugin/retrier.py index 05ee423..7732402 100644 --- a/watchtower-plugin/retrier.py +++ b/watchtower-plugin/retrier.py @@ -63,7 +63,11 @@ class Retrier: except SignatureError as e: tower_update["status"] = "misbehaving" - tower_update["invalid_appointment"] = (appointment_dict, e.kwargs.get("signature")) + tower_update["misbehaving_proof"] = { + "appointment": appointment_dict, + "signature": e.kwargs.get("signature"), + "recovered_id": e.kwargs.get("recovered_id"), + } except TowerConnectionError: tower_update["status"] = "temporarily unreachable" @@ -71,6 +75,9 @@ class Retrier: except TowerResponseError as e: tower_update["status"] = e.kwargs.get("status") + if e.kwargs.get("invalid_appointment"): + tower_update["invalid_appointment"] = (appointment_dict, signature) + if tower_update["status"] in ["reachable", "misbehaving"]: tower_update["pending_appointment"] = ([appointment_dict, signature], "remove") diff --git a/watchtower-plugin/tower_info.py b/watchtower-plugin/tower_info.py index e8b8597..3cf0642 100644 --- a/watchtower-plugin/tower_info.py +++ b/watchtower-plugin/tower_info.py @@ -1,32 +1,13 @@ class TowerInfo: - def __init__( - self, - netaddr, - available_slots, - status="reachable", - appointments=None, - pending_appointments=None, - invalid_appointments=None, - ): - + def __init__(self, netaddr, available_slots, status="reachable"): self.netaddr = netaddr self.available_slots = available_slots self.status = status - if not appointments: - self.appointments = {} - else: - self.appointments = appointments - - if not pending_appointments: - self.pending_appointments = [] - else: - self.pending_appointments = pending_appointments - - if not invalid_appointments: - self.invalid_appointments = [] - else: - self.invalid_appointments = invalid_appointments + self.appointments = {} + self.pending_appointments = [] + self.invalid_appointments = [] + self.misbehaving_proof = None @classmethod def from_dict(cls, tower_data): @@ -36,6 +17,7 @@ class TowerInfo: appointments = tower_data.get("appointments") pending_appointments = tower_data.get("pending_appointments") invalid_appointments = tower_data.get("invalid_appointments") + misbehaving_proof = tower_data.get("misbehaving_proof") if any( v is None @@ -43,7 +25,13 @@ class TowerInfo: ): raise ValueError("Wrong appointment data, some fields are missing") - return cls(netaddr, available_slots, status, appointments, pending_appointments, invalid_appointments) + tower = cls(netaddr, available_slots, status) + tower.appointments = appointments + tower.pending_appointments = pending_appointments + tower.invalid_appointments = invalid_appointments + tower.misbehaving_proof = misbehaving_proof + + return tower def to_dict(self): return self.__dict__ diff --git a/watchtower-plugin/watchtower.py b/watchtower-plugin/watchtower.py index b46837b..97a7221 100755 --- a/watchtower-plugin/watchtower.py +++ b/watchtower-plugin/watchtower.py @@ -71,6 +71,9 @@ class WTClient: if "invalid_appointment" in tower_update: tower_info.invalid_appointments.append(list(tower_update.get("invalid_appointment"))) + if "misbehaving_proof" in tower_update: + tower_info.misbehaving_proof = tower_update.get("misbehaving_proof") + self.towers[tower_id] = tower_info.get_summary() self.db_manager.store_tower_record(tower_id, tower_info) self.lock.release() @@ -280,7 +283,11 @@ def on_commitment_revocation(plugin, **kwargs): except SignatureError as e: tower_update["status"] = "misbehaving" - tower_update["invalid_appointment"] = (appointment.to_dict(), e.kwargs.get("signature")) + tower_update["misbehaving_proof"] = { + "appointment": appointment.to_dict(), + "signature": e.kwargs.get("signature"), + "recovered_id": e.kwargs.get("recovered_id"), + } except TowerConnectionError: # All TowerConnectionError are transitory. Connections are tried on register so URLs cannot be malformed. @@ -300,6 +307,9 @@ def on_commitment_revocation(plugin, **kwargs): if tower_update["status"] == "temporarily unreachable": tower_update["retry"] = True + if e.kwargs.get("invalid_appointment"): + tower_update["invalid_appointment"] = (appointment.to_dict(), signature) + finally: # Update memory and TowersDB plugin.wt_client.update_tower_state(tower_id, tower_update)