mirror of
https://github.com/aljazceru/python-teos.git
synced 2025-12-17 22:24:23 +01:00
Refactors Inspector so it returns exceptions for invalid data instead of error codes
Defines InspectionFailed, and exception that it's raised for invalid parameters. The design looks much cleaner now.
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import re
|
||||
|
||||
import common.cryptographer
|
||||
from common.logger import Logger
|
||||
from common.constants import LOCATOR_LEN_HEX
|
||||
from common.appointment import Appointment
|
||||
|
||||
from teos import errors, LOG_PREFIX
|
||||
from common.logger import Logger
|
||||
from common.appointment import Appointment
|
||||
|
||||
logger = Logger(actor="Inspector", log_name_prefix=LOG_PREFIX)
|
||||
common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_PREFIX)
|
||||
@@ -19,6 +19,14 @@ common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_
|
||||
BLOCKS_IN_A_MONTH = 4320 # 4320 = roughly a month in blocks
|
||||
|
||||
|
||||
class InspectionFailed(Exception):
|
||||
"""Raise this the inspector finds a problem with any of the appointment fields"""
|
||||
|
||||
def __init__(self, erno, reason):
|
||||
self.erno = erno
|
||||
self.reason = reason
|
||||
|
||||
|
||||
class Inspector:
|
||||
"""
|
||||
The :class:`Inspector` class is in charge of verifying that the appointment data provided by the user is correct.
|
||||
@@ -42,43 +50,29 @@ class Inspector:
|
||||
|
||||
|
||||
Returns:
|
||||
:obj:`Appointment <teos.appointment.Appointment>` or :obj:`tuple`: An appointment initialized with the
|
||||
provided data if it is correct.
|
||||
:obj:`Appointment <teos.appointment.Appointment>`: An appointment initialized with the
|
||||
provided data.
|
||||
|
||||
Returns a tuple ``(return code, message)`` describing the error otherwise.
|
||||
|
||||
Errors are defined in :mod:`Errors <teos.errors>`.
|
||||
Raises:
|
||||
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
|
||||
"""
|
||||
|
||||
if appointment_data is None:
|
||||
return errors.APPOINTMENT_EMPTY_FIELD, "empty appointment received"
|
||||
raise InspectionFailed(errors.APPOINTMENT_EMPTY_FIELD, "empty appointment received")
|
||||
elif not isinstance(appointment_data, dict):
|
||||
raise InspectionFailed(errors.APPOINTMENT_WRONG_FIELD, "wrong appointment format")
|
||||
|
||||
block_height = self.block_processor.get_block_count()
|
||||
if block_height is None:
|
||||
raise InspectionFailed(errors.UNKNOWN_JSON_RPC_EXCEPTION, "Unexpected error occurred")
|
||||
|
||||
if block_height is not None:
|
||||
rcode, message = self.check_locator(appointment_data.get("locator"))
|
||||
self.check_locator(appointment_data.get("locator"))
|
||||
self.check_start_time(appointment_data.get("start_time"), block_height)
|
||||
self.check_end_time(appointment_data.get("end_time"), appointment_data.get("start_time"), block_height)
|
||||
self.check_to_self_delay(appointment_data.get("to_self_delay"))
|
||||
self.check_blob(appointment_data.get("encrypted_blob"))
|
||||
|
||||
if rcode == 0:
|
||||
rcode, message = self.check_start_time(appointment_data.get("start_time"), block_height)
|
||||
if rcode == 0:
|
||||
rcode, message = self.check_end_time(
|
||||
appointment_data.get("end_time"), appointment_data.get("start_time"), block_height
|
||||
)
|
||||
if rcode == 0:
|
||||
rcode, message = self.check_to_self_delay(appointment_data.get("to_self_delay"))
|
||||
if rcode == 0:
|
||||
rcode, message = self.check_blob(appointment_data.get("encrypted_blob"))
|
||||
|
||||
if rcode == 0:
|
||||
r = Appointment.from_dict(appointment_data)
|
||||
else:
|
||||
r = (rcode, message)
|
||||
|
||||
else:
|
||||
# In case of an unknown exception, assign a special rcode and reason.
|
||||
r = (errors.UNKNOWN_JSON_RPC_EXCEPTION, "Unexpected error occurred")
|
||||
|
||||
return r
|
||||
return Appointment.from_dict(appointment_data)
|
||||
|
||||
@staticmethod
|
||||
def check_locator(locator):
|
||||
@@ -90,40 +84,23 @@ class Inspector:
|
||||
Args:
|
||||
locator (:obj:`str`): the locator to be checked.
|
||||
|
||||
Returns:
|
||||
:obj:`tuple`: A tuple (return code, message) as follows:
|
||||
|
||||
- ``(0, None)`` if the ``locator`` is correct.
|
||||
- ``!= (0, None)`` otherwise.
|
||||
|
||||
The possible return errors are: ``APPOINTMENT_EMPTY_FIELD``, ``APPOINTMENT_WRONG_FIELD_TYPE``,
|
||||
``APPOINTMENT_WRONG_FIELD_SIZE``, and ``APPOINTMENT_WRONG_FIELD_FORMAT``.
|
||||
Raises:
|
||||
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
|
||||
"""
|
||||
|
||||
message = None
|
||||
rcode = 0
|
||||
|
||||
if locator is None:
|
||||
rcode = errors.APPOINTMENT_EMPTY_FIELD
|
||||
message = "empty locator received"
|
||||
raise InspectionFailed(errors.APPOINTMENT_EMPTY_FIELD, "empty locator received")
|
||||
|
||||
elif type(locator) != str:
|
||||
rcode = errors.APPOINTMENT_WRONG_FIELD_TYPE
|
||||
message = "wrong locator data type ({})".format(type(locator))
|
||||
raise InspectionFailed(
|
||||
errors.APPOINTMENT_WRONG_FIELD_TYPE, "wrong locator data type ({})".format(type(locator))
|
||||
)
|
||||
|
||||
elif len(locator) != LOCATOR_LEN_HEX:
|
||||
rcode = errors.APPOINTMENT_WRONG_FIELD_SIZE
|
||||
message = "wrong locator size ({})".format(len(locator))
|
||||
# TODO: #12-check-txid-regexp
|
||||
raise InspectionFailed(errors.APPOINTMENT_WRONG_FIELD_SIZE, "wrong locator size ({})".format(len(locator)))
|
||||
|
||||
elif re.search(r"^[0-9A-Fa-f]+$", locator) is None:
|
||||
rcode = errors.APPOINTMENT_WRONG_FIELD_FORMAT
|
||||
message = "wrong locator format ({})".format(locator)
|
||||
|
||||
if message is not None:
|
||||
logger.error(message)
|
||||
|
||||
return rcode, message
|
||||
raise InspectionFailed(errors.APPOINTMENT_WRONG_FIELD_FORMAT, "wrong locator format ({})".format(locator))
|
||||
|
||||
@staticmethod
|
||||
def check_start_time(start_time, block_height):
|
||||
@@ -136,50 +113,35 @@ class Inspector:
|
||||
start_time (:obj:`int`): the block height at which the tower is requested to start watching for breaches.
|
||||
block_height (:obj:`int`): the chain height.
|
||||
|
||||
Returns:
|
||||
:obj:`tuple`: A tuple (return code, message) as follows:
|
||||
|
||||
- ``(0, None)`` if the ``start_time`` is correct.
|
||||
- ``!= (0, None)`` otherwise.
|
||||
|
||||
The possible return errors are: ``APPOINTMENT_EMPTY_FIELD``, ``APPOINTMENT_WRONG_FIELD_TYPE``, and
|
||||
``APPOINTMENT_FIELD_TOO_SMALL``.
|
||||
Raises:
|
||||
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
|
||||
"""
|
||||
|
||||
message = None
|
||||
rcode = 0
|
||||
|
||||
# TODO: What's too close to the current height is not properly defined. Right now any appointment that is in the
|
||||
# future will be accepted (even if it's only one block away).
|
||||
|
||||
t = type(start_time)
|
||||
|
||||
if start_time is None:
|
||||
rcode = errors.APPOINTMENT_EMPTY_FIELD
|
||||
message = "empty start_time received"
|
||||
raise InspectionFailed(errors.APPOINTMENT_EMPTY_FIELD, "empty start_time received")
|
||||
|
||||
elif t != int:
|
||||
rcode = errors.APPOINTMENT_WRONG_FIELD_TYPE
|
||||
message = "wrong start_time data type ({})".format(t)
|
||||
elif type(start_time) != int:
|
||||
raise InspectionFailed(
|
||||
errors.APPOINTMENT_WRONG_FIELD_TYPE, "wrong start_time data type ({})".format(type(start_time))
|
||||
)
|
||||
|
||||
elif start_time <= block_height:
|
||||
rcode = errors.APPOINTMENT_FIELD_TOO_SMALL
|
||||
if start_time < block_height:
|
||||
message = "start_time is in the past"
|
||||
else:
|
||||
message = (
|
||||
"start_time is too close to current height. "
|
||||
"Accepted times are: [current_height+1, current_height+6]"
|
||||
)
|
||||
elif start_time < block_height:
|
||||
raise InspectionFailed(errors.APPOINTMENT_FIELD_TOO_SMALL, "start_time is in the past")
|
||||
|
||||
elif start_time == block_height:
|
||||
raise InspectionFailed(
|
||||
errors.APPOINTMENT_FIELD_TOO_SMALL,
|
||||
"start_time is too close to current height. Accepted times are: [current_height+1, current_height+6]",
|
||||
)
|
||||
|
||||
elif start_time > block_height + 6:
|
||||
rcode = errors.APPOINTMENT_FIELD_TOO_BIG
|
||||
message = "start_time is too far in the future. Accepted start times are up to 6 blocks in the future"
|
||||
|
||||
if message is not None:
|
||||
logger.error(message)
|
||||
|
||||
return rcode, message
|
||||
raise InspectionFailed(
|
||||
errors.APPOINTMENT_FIELD_TOO_BIG,
|
||||
"start_time is too far in the future. Accepted start times are up to 6 blocks in the future",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def check_end_time(end_time, start_time, block_height):
|
||||
@@ -193,54 +155,36 @@ class Inspector:
|
||||
start_time (:obj:`int`): the block height at which the tower is requested to start watching for breaches.
|
||||
block_height (:obj:`int`): the chain height.
|
||||
|
||||
Returns:
|
||||
:obj:`tuple`: A tuple (return code, message) as follows:
|
||||
|
||||
- ``(0, None)`` if the ``end_time`` is correct.
|
||||
- ``!= (0, None)`` otherwise.
|
||||
|
||||
The possible return errors are: ``APPOINTMENT_EMPTY_FIELD``, ``APPOINTMENT_WRONG_FIELD_TYPE``, and
|
||||
``APPOINTMENT_FIELD_TOO_SMALL``.
|
||||
Raises:
|
||||
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
|
||||
"""
|
||||
|
||||
message = None
|
||||
rcode = 0
|
||||
|
||||
# TODO: What's too close to the current height is not properly defined. Right now any appointment that ends in
|
||||
# the future will be accepted (even if it's only one block away).
|
||||
|
||||
t = type(end_time)
|
||||
|
||||
if end_time is None:
|
||||
rcode = errors.APPOINTMENT_EMPTY_FIELD
|
||||
message = "empty end_time received"
|
||||
raise InspectionFailed(errors.APPOINTMENT_EMPTY_FIELD, "empty end_time received")
|
||||
|
||||
elif t != int:
|
||||
rcode = errors.APPOINTMENT_WRONG_FIELD_TYPE
|
||||
message = "wrong end_time data type ({})".format(t)
|
||||
elif type(end_time) != int:
|
||||
raise InspectionFailed(
|
||||
errors.APPOINTMENT_WRONG_FIELD_TYPE, "wrong end_time data type ({})".format(type(end_time))
|
||||
)
|
||||
|
||||
elif end_time > block_height + BLOCKS_IN_A_MONTH: # 4320 = roughly a month in blocks
|
||||
rcode = errors.APPOINTMENT_FIELD_TOO_BIG
|
||||
message = "end_time should be within the next month (<= current_height + 4320)"
|
||||
raise InspectionFailed(
|
||||
errors.APPOINTMENT_FIELD_TOO_BIG, "end_time should be within the next month (<= current_height + 4320)"
|
||||
)
|
||||
elif start_time > end_time:
|
||||
raise InspectionFailed(errors.APPOINTMENT_FIELD_TOO_SMALL, "end_time is smaller than start_time")
|
||||
|
||||
elif start_time >= end_time:
|
||||
rcode = errors.APPOINTMENT_FIELD_TOO_SMALL
|
||||
if start_time > end_time:
|
||||
message = "end_time is smaller than start_time"
|
||||
else:
|
||||
message = "end_time is equal to start_time"
|
||||
elif start_time == end_time:
|
||||
raise InspectionFailed(errors.APPOINTMENT_FIELD_TOO_SMALL, "end_time is equal to start_time")
|
||||
|
||||
elif block_height >= end_time:
|
||||
rcode = errors.APPOINTMENT_FIELD_TOO_SMALL
|
||||
if block_height > end_time:
|
||||
message = "end_time is in the past"
|
||||
else:
|
||||
message = "end_time is too close to current height"
|
||||
elif block_height > end_time:
|
||||
raise InspectionFailed(errors.APPOINTMENT_FIELD_TOO_SMALL, "end_time is in the past")
|
||||
|
||||
if message is not None:
|
||||
logger.error(message)
|
||||
|
||||
return rcode, message
|
||||
elif block_height == end_time:
|
||||
raise InspectionFailed(errors.APPOINTMENT_FIELD_TOO_SMALL, "end_time is too close to current height")
|
||||
|
||||
def check_to_self_delay(self, to_self_delay):
|
||||
"""
|
||||
@@ -252,46 +196,32 @@ class Inspector:
|
||||
to_self_delay (:obj:`int`): The ``to_self_delay`` encoded in the ``csv`` of the ``htlc`` that this
|
||||
appointment is covering.
|
||||
|
||||
Returns:
|
||||
:obj:`tuple`: A tuple (return code, message) as follows:
|
||||
|
||||
- ``(0, None)`` if the ``to_self_delay`` is correct.
|
||||
- ``!= (0, None)`` otherwise.
|
||||
|
||||
The possible return errors are: ``APPOINTMENT_EMPTY_FIELD``, ``APPOINTMENT_WRONG_FIELD_TYPE``, and
|
||||
``APPOINTMENT_FIELD_TOO_SMALL``.
|
||||
Raises:
|
||||
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
|
||||
"""
|
||||
|
||||
message = None
|
||||
rcode = 0
|
||||
|
||||
t = type(to_self_delay)
|
||||
|
||||
if to_self_delay is None:
|
||||
rcode = errors.APPOINTMENT_EMPTY_FIELD
|
||||
message = "empty to_self_delay received"
|
||||
raise InspectionFailed(errors.APPOINTMENT_EMPTY_FIELD, "empty to_self_delay received")
|
||||
|
||||
elif t != int:
|
||||
rcode = errors.APPOINTMENT_WRONG_FIELD_TYPE
|
||||
message = "wrong to_self_delay data type ({})".format(t)
|
||||
elif type(to_self_delay) != int:
|
||||
raise InspectionFailed(
|
||||
errors.APPOINTMENT_WRONG_FIELD_TYPE, "wrong to_self_delay data type ({})".format(type(to_self_delay))
|
||||
)
|
||||
|
||||
elif to_self_delay > pow(2, 32):
|
||||
rcode = errors.APPOINTMENT_FIELD_TOO_BIG
|
||||
message = "to_self_delay must fit the transaction nLockTime field ({} > {})".format(
|
||||
to_self_delay, pow(2, 32)
|
||||
raise InspectionFailed(
|
||||
errors.APPOINTMENT_FIELD_TOO_BIG,
|
||||
"to_self_delay must fit the transaction nLockTime field ({} > {})".format(to_self_delay, pow(2, 32)),
|
||||
)
|
||||
|
||||
elif to_self_delay < self.min_to_self_delay:
|
||||
rcode = errors.APPOINTMENT_FIELD_TOO_SMALL
|
||||
message = "to_self_delay too small. The to_self_delay should be at least {} (current: {})".format(
|
||||
self.min_to_self_delay, to_self_delay
|
||||
raise InspectionFailed(
|
||||
errors.APPOINTMENT_FIELD_TOO_SMALL,
|
||||
"to_self_delay too small. The to_self_delay should be at least {} (current: {})".format(
|
||||
self.min_to_self_delay, to_self_delay
|
||||
),
|
||||
)
|
||||
|
||||
if message is not None:
|
||||
logger.error(message)
|
||||
|
||||
return rcode, message
|
||||
|
||||
# ToDo: #6-define-checks-encrypted-blob
|
||||
@staticmethod
|
||||
def check_blob(encrypted_blob):
|
||||
@@ -301,34 +231,19 @@ class Inspector:
|
||||
Args:
|
||||
encrypted_blob (:obj:`str`): the encrypted blob to be checked (hex encoded).
|
||||
|
||||
Returns:
|
||||
:obj:`tuple`: A tuple (return code, message) as follows:
|
||||
|
||||
- ``(0, None)`` if the ``encrypted_blob`` is correct.
|
||||
- ``!= (0, None)`` otherwise.
|
||||
|
||||
The possible return errors are: ``APPOINTMENT_EMPTY_FIELD``, ``APPOINTMENT_WRONG_FIELD_TYPE``, and
|
||||
``APPOINTMENT_WRONG_FIELD_FORMAT``.
|
||||
Raises:
|
||||
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
|
||||
"""
|
||||
|
||||
message = None
|
||||
rcode = 0
|
||||
|
||||
t = type(encrypted_blob)
|
||||
|
||||
if encrypted_blob is None:
|
||||
rcode = errors.APPOINTMENT_EMPTY_FIELD
|
||||
message = "empty encrypted_blob received"
|
||||
raise InspectionFailed(errors.APPOINTMENT_EMPTY_FIELD, "empty encrypted_blob received")
|
||||
|
||||
elif t != str:
|
||||
rcode = errors.APPOINTMENT_WRONG_FIELD_TYPE
|
||||
message = "wrong encrypted_blob data type ({})".format(t)
|
||||
elif type(encrypted_blob) != str:
|
||||
raise InspectionFailed(
|
||||
errors.APPOINTMENT_WRONG_FIELD_TYPE, "wrong encrypted_blob data type ({})".format(type(encrypted_blob))
|
||||
)
|
||||
|
||||
elif re.search(r"^[0-9A-Fa-f]+$", encrypted_blob) is None:
|
||||
rcode = errors.APPOINTMENT_WRONG_FIELD_FORMAT
|
||||
message = "wrong encrypted_blob format ({})".format(encrypted_blob)
|
||||
|
||||
if message is not None:
|
||||
logger.error(message)
|
||||
|
||||
return rcode, message
|
||||
raise InspectionFailed(
|
||||
errors.APPOINTMENT_WRONG_FIELD_FORMAT, "wrong encrypted_blob format ({})".format(encrypted_blob)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user