Fixes comments, docstrings and some renamings

This commit is contained in:
Sergi Delgado Segura
2020-04-02 15:20:04 +02:00
parent 39f2628b79
commit fe73ee7298
20 changed files with 213 additions and 233 deletions

View File

@@ -9,18 +9,17 @@ class Appointment:
The :class:`Appointment` contains the information regarding an appointment between a client and the Watchtower.
Args:
locator (:mod:`str`): A 16-byte hex-encoded value used by the tower to detect channel breaches. It serves as a
locator (:obj:`str`): A 16-byte hex-encoded value used by the tower to detect channel breaches. It serves as a
trigger for the tower to decrypt and broadcast the penalty transaction.
start_time (:mod:`int`): The block height where the tower is hired to start watching for breaches.
end_time (:mod:`int`): The block height where the tower will stop watching for breaches.
to_self_delay (:mod:`int`): The ``to_self_delay`` encoded in the ``csv`` of the ``htlc`` that this appointment
is covering.
start_time (:obj:`int`): The block height where the tower is hired to start watching for breaches.
end_time (:obj:`int`): The block height where the tower will stop watching for breaches.
to_self_delay (:obj:`int`): The ``to_self_delay`` encoded in the ``csv`` of the ``to_remote`` output of the
commitment transaction that this appointment is covering.
encrypted_blob (:obj:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>`): An ``EncryptedBlob`` object
containing an encrypted penalty transaction. The tower will decrypt it and broadcast the penalty transaction
upon seeing a breach on the blockchain.
"""
# DISCUSS: 35-appointment-checks
def __init__(self, locator, start_time, end_time, to_self_delay, encrypted_blob):
self.locator = locator
self.start_time = start_time # ToDo: #4-standardize-appointment-fields
@@ -36,7 +35,7 @@ class Appointment:
This method is useful to load data from a database.
Args:
appointment_data (:mod:`dict`): a dictionary containing the following keys:
appointment_data (:obj:`dict`): a dictionary containing the following keys:
``{locator, start_time, end_time, to_self_delay, encrypted_blob}``
Returns:
@@ -62,11 +61,10 @@ class Appointment:
def to_dict(self):
"""
Exports an appointment as a dictionary.
Encodes an appointment as a dictionary.
Returns:
:obj:`dict`: A dictionary containing the appointment attributes.
"""
# ToDO: #3-improve-appointment-structure
@@ -90,7 +88,7 @@ class Appointment:
All values are big endian.
Returns:
:mod:`bytes`: The serialized data to be signed.
:obj:`bytes`: The serialized data to be signed.
"""
return (
unhexlify(self.locator)

View File

@@ -21,14 +21,14 @@ class ConfigLoader:
data_dir (:obj:`str`): the path to the data directory where the configuration file may be found.
conf_file_path (:obj:`str`): the path to the config file (the file may not exist).
conf_fields (:obj:`dict`): a dictionary populated with the configuration params and the expected types.
follows the same format as default_conf.
It follows the same format as default_conf.
command_line_conf (:obj:`dict`): a dictionary containing the command line parameters that may replace the
ones in default / config file.
"""
def __init__(self, data_dir, conf_file_name, default_conf, command_line_conf):
self.data_dir = data_dir
self.conf_file_path = self.data_dir + conf_file_name
self.conf_file_path = os.path.join(self.data_dir, conf_file_name)
self.conf_fields = default_conf
self.command_line_conf = command_line_conf
@@ -36,13 +36,13 @@ class ConfigLoader:
"""
Builds a config dictionary from command line, config file and default configuration parameters.
The priority if as follows:
The priority is as follows:
- command line
- config file
- defaults
Returns:
obj:`dict`: a dictionary containing all the configuration parameters.
:obj:`dict`: a dictionary containing all the configuration parameters.
"""
@@ -50,6 +50,7 @@ class ConfigLoader:
file_config = configparser.ConfigParser()
file_config.read(self.conf_file_path)
# Load parameters and cast them to int if necessary
if file_config:
for sec in file_config.sections():
for k, v in file_config.items(sec):
@@ -82,10 +83,10 @@ class ConfigLoader:
Returns:
:obj:`dict`: A dictionary with the same keys as the provided one, but containing only the "value" field as
value if the provided ``conf_fields`` where correct.
value if the provided ``conf_fields`` are correct.
Raises:
ValueError: If any of the dictionary elements does not have the expected type
:obj:`ValueError`: If any of the dictionary elements does not have the expected type.
"""
conf_dict = {}
@@ -110,5 +111,5 @@ class ConfigLoader:
"""
for key, field in self.conf_fields.items():
if field.get("path") is True and isinstance(field.get("value"), str):
if field.get("path") and isinstance(field.get("value"), str):
self.conf_fields[key]["value"] = os.path.join(self.data_dir, self.conf_fields[key]["value"])

View File

@@ -6,14 +6,14 @@ from coincurve import PrivateKey, PublicKey
from cryptography.exceptions import InvalidTag
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from common.tools import check_sha256_hex_format
from common.tools import is_256b_hex_str
LN_MESSAGE_PREFIX = b"Lightning Signed Message:"
def sha256d(message):
"""
Compute the sha245d (double sha256) of a given by message.
Compute the sha256 (double sha256) of a given by message.
Args:
message(:obj:`bytes`): the message to be used as input to the hash function.
@@ -46,10 +46,9 @@ def hash_160(message):
# NOTCOVERED
def sigrec_encode(rsig_rid):
"""
Encodes a pk-recoverable signature to be used in LN. ```rsig_rid`` can be obtained trough
Encodes a pk-recoverable signature to be used in LN. ``rsig_rid`` can be obtained trough
``PrivateKey.sign_recoverable``. The required format has the recovery id as the last byte, and for signing LN
messages we need it as the first.
From: https://twitter.com/rusty_twit/status/1182102005914800128
messages we need it as the first. From: https://twitter.com/rusty_twit/status/1182102005914800128
Args:
rsig_rid(:obj:`bytes`): the signature to be encoded.
@@ -94,7 +93,7 @@ logger = None
class Cryptographer:
"""
The :class:`Cryptographer` is the class in charge of all the cryptography in the tower.
The :class:`Cryptographer` is in charge of all the cryptography in the tower.
"""
@staticmethod
@@ -104,21 +103,21 @@ class Cryptographer:
formatted.
Args:
data(:mod:`str`): the data to be encrypted.
secret(:mod:`str`): the secret used to derive the encryption key.
data(:obj:`str`): the data to be encrypted.
secret(:obj:`str`): the secret used to derive the encryption key.
Returns:
:obj:`bool`: Whether or not the ``key`` and ``data`` are properly formatted.
Raises:
ValueError: if either the ``key`` or ``data`` is not properly formatted.
:obj:`ValueError`: if either the ``key`` or ``data`` is not properly formatted.
"""
if len(data) % 2:
error = "Incorrect (Odd-length) value"
raise ValueError(error)
if not check_sha256_hex_format(secret):
if not is_256b_hex_str(secret):
error = "Secret must be a 32-byte hex value (64 hex chars)"
raise ValueError(error)
@@ -127,16 +126,19 @@ class Cryptographer:
@staticmethod
def encrypt(blob, secret):
"""
Encrypts a given :mod:`Blob <common.cli.blob.Blob>` data using ``CHACHA20POLY1305``.
Encrypts a given :obj:`Blob <common.cli.blob.Blob>` data using ``CHACHA20POLY1305``.
``SHA256(secret)`` is used as ``key``, and ``0 (12-byte)`` as ``iv``.
Args:
blob (:mod:`Blob <common.cli.blob.Blob>`): a ``Blob`` object containing a raw penalty transaction.
secret (:mod:`str`): a value to used to derive the encryption key. Should be the dispute txid.
blob (:obj:`Blob <common.cli.blob.Blob>`): a ``Blob`` object containing a raw penalty transaction.
secret (:obj:`str`): a value to used to derive the encryption key. Should be the dispute txid.
Returns:
:obj:`str`: The encrypted data (hex encoded).
Raises:
:obj:`ValueError`: if either the ``secret`` or ``blob`` is not properly formatted.
"""
Cryptographer.check_data_key_format(blob.data, secret)
@@ -162,17 +164,20 @@ class Cryptographer:
# ToDo: #20-test-tx-decrypting-edge-cases
def decrypt(encrypted_blob, secret):
"""
Decrypts a given :mod:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>` using ``CHACHA20POLY1305``.
Decrypts a given :obj:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>` using ``CHACHA20POLY1305``.
``SHA256(secret)`` is used as ``key``, and ``0 (12-byte)`` as ``iv``.
Args:
encrypted_blob(:mod:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>`): an ``EncryptedBlob``
encrypted_blob(:obj:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>`): an ``EncryptedBlob``
potentially containing a penalty transaction.
secret (:mod:`str`): a value to used to derive the decryption key. Should be the dispute txid.
secret (:obj:`str`): a value to used to derive the decryption key. Should be the dispute txid.
Returns:
:obj:`str`: The decrypted data (hex encoded).
Raises:
:obj:`ValueError`: if either the ``secret`` or ``encrypted_blob`` is not properly formatted.
"""
Cryptographer.check_data_key_format(encrypted_blob.data, secret)
@@ -234,17 +239,14 @@ class Cryptographer:
@staticmethod
def load_private_key_der(sk_der):
"""
Creates a :mod:`PrivateKey` object from a given ``DER`` encoded private key.
Creates a :obj:`PrivateKey` from a given ``DER`` encoded private key.
Args:
sk_der(:mod:`str`): a private key encoded in ``DER`` format.
sk_der(:obj:`str`): a private key encoded in ``DER`` format.
Returns:
:mod:`PrivateKey`: A ``PrivateKey`` object.
Raises:
ValueError: if the provided ``pk_der`` data cannot be deserialized (wrong size or format).
TypeError: if the provided ``pk_der`` data is not a string.
:obj:`PrivateKey` or :obj:`None`: A ``PrivateKey`` object. if the private key can be loaded. `None`
otherwise.
"""
try:
sk = PrivateKey.from_der(sk_der)
@@ -261,14 +263,14 @@ class Cryptographer:
@staticmethod
def sign(message, sk):
"""
Signs a given data using a given secret key using ECDSA.
Signs a given data using a given secret key using ECDSA over secp256k1.
Args:
message(:obj:`bytes`): the data to be signed.
sk(:obj:`PrivateKey`): the ECDSA secret key used to signed the data.
Returns:
:obj:`str`: The zbase32 signature of the given message.
:obj:`str` or :obj:`None`: The zbase32 signature of the given message is it can be signed. `None` otherwise.
"""
if not isinstance(message, bytes):
@@ -295,7 +297,7 @@ class Cryptographer:
zb32_sig(:obj:`str`): the zbase32 signature of the message.
Returns:
:obj:`PublicKey`: The recovered public key.
:obj:`PublicKey` or :obj:`None`: The recovered public key if it can be recovered. `None` otherwise.
"""
if not isinstance(message, bytes):
@@ -345,13 +347,14 @@ class Cryptographer:
@staticmethod
def get_compressed_pk(pk):
"""
Computes a compressed, hex encoded, public key given a ``PublicKey`` object.
Computes a compressed, hex-encoded, public key given a ``PublicKey``.
Args:
pk(:obj:`PublicKey`): a given public key.
Returns:
:obj:`str`: A compressed, hex encoded, public key (33-byte long)
:obj:`str` or :obj:`None`: A compressed, hex-encoded, public key (33-byte long) if it can be compressed.
`None` oterwise.
"""
if not isinstance(pk, PublicKey):

View File

@@ -15,9 +15,10 @@ class _StructuredMessage:
class Logger:
"""
The :class:`Logger` is the class in charge of logging events into the log file.
The :class:`Logger` is in charge of logging events into the log file.
Args:
log_name_prefix (:obj:`str`): the prefix of the logger where the data will be stored in (server, client, ...).
actor (:obj:`str`): the system actor that is logging the event (e.g. ``Watcher``, ``Cryptographer``, ...).
"""
@@ -52,7 +53,7 @@ class Logger:
Args:
msg (:obj:`str`): the message to be logged.
kwargs: a ``key:value`` collection parameters to be added to the output.
kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output.
"""
self.f_logger.info(self._create_file_message(msg, **kwargs))
@@ -64,7 +65,7 @@ class Logger:
Args:
msg (:obj:`str`): the message to be logged.
kwargs: a ``key:value`` collection parameters to be added to the output.
kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output.
"""
self.f_logger.debug(self._create_file_message(msg, **kwargs))
@@ -76,7 +77,7 @@ class Logger:
Args:
msg (:obj:`str`): the message to be logged.
kwargs: a ``key:value`` collection parameters to be added to the output.
kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output.
"""
self.f_logger.error(self._create_file_message(msg, **kwargs))
@@ -88,7 +89,7 @@ class Logger:
Args:
msg (:obj:`str`): the message to be logged.
kwargs: a ``key:value`` collection parameters to be added to the output.
kwargs (:obj:`dict`): a ``key:value`` collection parameters to be added to the output.
"""
self.f_logger.warning(self._create_file_message(msg, **kwargs))

View File

@@ -4,21 +4,21 @@ from pathlib import Path
from common.constants import LOCATOR_LEN_HEX
def check_compressed_pk_format(compressed_pk):
def is_compressed_pk(value):
"""
Checks if a given value is a 33-byte hex encoded string.
Checks if a given value is a 33-byte hex-encoded string starting by 02 or 03.
Args:
compressed_pk(:obj:`str`): the value to be checked.
value(:obj:`str`): the value to be checked.
Returns:
:obj:`bool`: Whether or not the value matches the format.
"""
return isinstance(compressed_pk, str) and re.match(r"^0[2-3][0-9A-Fa-f]{64}$", compressed_pk) is not None
return isinstance(value, str) and re.match(r"^0[2-3][0-9A-Fa-f]{64}$", value) is not None
def check_sha256_hex_format(value):
def is_256b_hex_str(value):
"""
Checks if a given value is a 32-byte hex encoded string.
@@ -31,7 +31,7 @@ def check_sha256_hex_format(value):
return isinstance(value, str) and re.match(r"^[0-9A-Fa-f]{64}$", value) is not None
def check_locator_format(value):
def is_locator(value):
"""
Checks if a given value is a 16-byte hex encoded string.
@@ -61,7 +61,7 @@ def setup_data_folder(data_folder):
Create a data folder for either the client or the server side if the folder does not exists.
Args:
data_folder (:obj:`str`): the path of the folder
data_folder (:obj:`str`): the path of the folder.
"""
Path(data_folder).mkdir(parents=True, exist_ok=True)
@@ -69,9 +69,12 @@ def setup_data_folder(data_folder):
def setup_logging(log_file_path, log_name_prefix):
"""
Setups a couple of loggers (console and file) given a prefix and a file path. The log names are:
Setups a couple of loggers (console and file) given a prefix and a file path.
prefix | _file_log and prefix | _console_log
The log names are:
prefix | _file_log
prefix | _console_log
Args:
log_file_path (:obj:`str`): the path of the file to output the file log.
@@ -80,10 +83,10 @@ def setup_logging(log_file_path, log_name_prefix):
if not isinstance(log_file_path, str):
print(log_file_path)
raise ValueError("Wrong log file path.")
raise ValueError("Wrong log file path")
if not isinstance(log_name_prefix, str):
raise ValueError("Wrong log file name.")
raise ValueError("Wrong log file name")
# Create the file logger
f_logger = logging.getLogger("{}_file_log".format(log_name_prefix))

View File

@@ -45,7 +45,7 @@ def get_remote_addr():
# NOTCOVERED: not sure how to monkey path this one. May be related to #77
def get_request_data_json(request):
"""
Gets the content of a json POST request and makes sure ir decodes to a Python dictionary.
Gets the content of a json POST request and makes sure it decodes to a dictionary.
Args:
request (:obj:`Request`): the request sent by the user.
@@ -54,7 +54,7 @@ def get_request_data_json(request):
:obj:`dict`: the dictionary parsed from the json request.
Raises:
:obj:`TypeError`: if the request is not json encoded or it does not decodes to a Python dictionary.
:obj:`TypeError`: if the request is not json encoded or it does not decodes to a dictionary.
"""
if request.is_json:
@@ -69,13 +69,14 @@ def get_request_data_json(request):
class API:
"""
The :class:`API` is in charge of the interface between the user and the tower. It handles and server user requests.
The :class:`API` is in charge of the interface between the user and the tower. It handles and serves user requests.
Args:
inspector (:obj:`Inspector <teos.inspector.Inspector>`): an ``Inspector`` instance to check the correctness of
the received data.
the received appointment data.
watcher (:obj:`Watcher <teos.watcher.Watcher>`): a ``Watcher`` instance to pass the requests to.
gatekeeper (:obj:`Watcher <teos.gatekeeper.Gatekeeper>`): a `Gatekeeper` instance in charge to gatekeep the API.
gatekeeper (:obj:`Watcher <teos.gatekeeper.Gatekeeper>`): a `Gatekeeper` instance in charge to control the user
access.
"""
def __init__(self, inspector, watcher, gatekeeper):
@@ -104,12 +105,11 @@ class API:
Users register by sending a public key to the proper endpoint. This is exploitable atm, but will be solved when
payments are introduced.
Returns:
:obj:`tuple`: A tuple containing the response (``json``) and response code (``int``). For accepted requests,
the ``rcode`` is always 200 and the response contains a json with the public key and number of slots in the
subscription. For rejected requests, the ``rcode`` is a 404 and the value contains an application specific
error, and an error message. Error messages can be found at :mod:`Errors <teos.errors>`.
:obj:`tuple`: A tuple containing the response (:obj:`str`) and response code (:obj:`int`). For accepted
requests, the ``rcode`` is always 200 and the response contains a json with the public key and number of
slots in the subscription. For rejected requests, the ``rcode`` is a 404 and the value contains an
application error, and an error message. Error messages can be found at :mod:`Errors <teos.errors>`.
"""
remote_addr = get_remote_addr()
@@ -150,12 +150,12 @@ class API:
Main endpoint of the Watchtower.
The client sends requests (appointments) to this endpoint to request a job to the Watchtower. Requests must be
json encoded and contain an ``appointment`` field and optionally a ``signature`` and ``public_key`` fields.
json encoded and contain an ``appointment`` and ``signature`` fields.
Returns:
:obj:`tuple`: A tuple containing the response (``json``) and response code (``int``). For accepted
appointments, the ``rcode`` is always 200 and the response contains the receipt signature. For rejected
appointments, the ``rcode`` is a 404 and the value contains an application specific error, and an error
:obj:`tuple`: A tuple containing the response (:obj:`str`) and response code (:obj:`int`). For accepted
appointments, the ``rcode`` is always 200 and the response contains the receipt signature (json). For
rejected appointments, the ``rcode`` is a 404 and the value contains an application error, and an error
message. Error messages can be found at :mod:`Errors <teos.errors>`.
"""
@@ -185,16 +185,16 @@ class API:
appointment_uuid = hash_160("{}{}".format(appointment.locator, user_pk))
appointment_summary = self.watcher.get_appointment_summary(appointment_uuid)
# For updates we only reserve the slot difference provided the new one is bigger.
if appointment_summary:
used_slots = ceil(appointment_summary.get("size") / ENCRYPTED_BLOB_MAX_SIZE_HEX)
required_slots = ceil(len(appointment.encrypted_blob.data) / ENCRYPTED_BLOB_MAX_SIZE_HEX)
slot_diff = required_slots - used_slots
# For updates we only reserve the slot difference provided the new one is bigger.
required_slots = slot_diff if slot_diff > 0 else 0
# For regular appointments 1 slot is reserved per ENCRYPTED_BLOB_MAX_SIZE_HEX block.
else:
# For regular appointments 1 slot is reserved per ENCRYPTED_BLOB_MAX_SIZE_HEX block.
slot_diff = 0
required_slots = ceil(len(appointment.encrypted_blob.data) / ENCRYPTED_BLOB_MAX_SIZE_HEX)
@@ -245,7 +245,9 @@ class API:
The information is requested by ``locator``.
Returns:
:obj:`dict`: A json formatted dictionary containing information about the requested appointment.
:obj:`str`: A json formatted dictionary containing information about the requested appointment.
Returns not found if the user does not have the requested appointment or the locator is invalid.
A ``status`` flag is added to the data provided by either the :obj:`Watcher <teos.watcher.Watcher>` or the
:obj:`Responder <teos.responder.Responder>` that signals the status of the appointment.
@@ -312,10 +314,8 @@ class API:
This endpoint should only be accessible by the administrator. Requests are only allowed from localhost.
Returns:
:obj:`dict`: A json formatted dictionary containing all the appointments hold by the
:obj:`Watcher <teos.watcher.Watcher>` (``watcher_appointments``) and by the
:obj:`Responder <teos.responder.Responder>` (``responder_trackers``).
:obj:`str`: A json formatted dictionary containing all the appointments hold by the ``Watcher``
(``watcher_appointments``) and by the ``Responder>`` (``responder_trackers``).
"""
# ToDo: #15-add-system-monitor

View File

@@ -19,7 +19,7 @@ TRIGGERED_APPOINTMENTS_PREFIX = "ta"
class AppointmentsDBM(DBManager):
"""
The :class:`AppointmentsDBM` is the class in charge of interacting with the appointments database (``LevelDB``).
The :class:`AppointmentsDBM` is in charge of interacting with the appointments database (``LevelDB``).
Keys and values are stored as bytes in the database but processed as strings by the manager.
The database is split in six prefixes:
@@ -36,8 +36,8 @@ class AppointmentsDBM(DBManager):
database will be create if the specified path does not contain one.
Raises:
ValueError: If the provided ``db_path`` is not a string.
plyvel.Error: If the db is currently unavailable (being used by another process).
:obj:`ValueError`: If the provided ``db_path`` is not a string.
:obj:`plyvel.Error`: If the db is currently unavailable (being used by another process).
"""
def __init__(self, db_path):
@@ -78,7 +78,11 @@ class AppointmentsDBM(DBManager):
def get_last_known_block(self, key):
"""
Loads the last known block given a key (either ``WATCHER_LAST_BLOCK_KEY`` or ``RESPONDER_LAST_BLOCK_KEY``).
Loads the last known block given a key.
Args:
key (:obj:`str`): the identifier of the db to look into (either ``WATCHER_LAST_BLOCK_KEY`` or
``RESPONDER_LAST_BLOCK_KEY``).
Returns:
:obj:`str` or :obj:`None`: A 16-byte hex-encoded str representing the last known block hash.
@@ -93,9 +97,12 @@ class AppointmentsDBM(DBManager):
return last_block
def load_watcher_appointment(self, key):
def load_watcher_appointment(self, uuid):
"""
Loads an appointment from the database using ``WATCHER_PREFIX`` as prefix to the given ``key``.
Loads an appointment from the database using ``WATCHER_PREFIX`` as prefix to the given ``uuid``.
Args:
uuid (:obj:`str`): the appointment's unique identifier.
Returns:
:obj:`dict`: A dictionary containing the appointment data if they ``key`` is found.
@@ -104,16 +111,19 @@ class AppointmentsDBM(DBManager):
"""
try:
data = self.load_entry(key, prefix=WATCHER_PREFIX)
data = self.load_entry(uuid, prefix=WATCHER_PREFIX)
data = json.loads(data)
except (TypeError, json.decoder.JSONDecodeError):
data = None
return data
def load_responder_tracker(self, key):
def load_responder_tracker(self, uuid):
"""
Loads a tracker from the database using ``RESPONDER_PREFIX`` as a prefix to the given ``key``.
Loads a tracker from the database using ``RESPONDER_PREFIX`` as a prefix to the given ``uuid``.
Args:
uuid (:obj:`str`): the tracker's unique identifier.
Returns:
:obj:`dict`: A dictionary containing the tracker data if they ``key`` is found.
@@ -122,7 +132,7 @@ class AppointmentsDBM(DBManager):
"""
try:
data = self.load_entry(key, prefix=RESPONDER_PREFIX)
data = self.load_entry(uuid, prefix=RESPONDER_PREFIX)
data = json.loads(data)
except (TypeError, json.decoder.JSONDecodeError):
data = None
@@ -134,7 +144,7 @@ class AppointmentsDBM(DBManager):
Loads all the appointments from the database (all entries with the ``WATCHER_PREFIX`` prefix).
Args:
include_triggered (:obj:`bool`): Whether to include the appointments flagged as triggered or not. ``False``
include_triggered (:obj:`bool`): whether to include the appointments flagged as triggered or not. ``False``
by default.
Returns:

View File

@@ -36,12 +36,12 @@ class Receipt:
class Carrier:
"""
The :class:`Carrier` is the class in charge of interacting with ``bitcoind`` to send/get transactions. It uses
:obj:`Receipt` objects to report about the sending outcome.
The :class:`Carrier` is in charge of interacting with ``bitcoind`` to send/get transactions. It uses :obj:`Receipt`
objects to report about the sending outcome.
Args:
btc_connect_params (:obj:`dict`): a dictionary with the parameters to connect to bitcoind
(rpc user, rpc passwd, host and port)
(rpc user, rpc password, host and port)
Attributes:
issued_receipts (:obj:`dict`): a dictionary of issued receipts to prevent resending the same transaction over
@@ -135,18 +135,17 @@ class Carrier:
Returns:
:obj:`dict` or :obj:`None`: A dictionary with the transaction data if the transaction can be found on the
chain.
Returns ``None`` otherwise.
chain. ``None`` otherwise.
"""
try:
tx_info = bitcoin_cli(self.btc_connect_params).getrawtransaction(txid, 1)
return tx_info
except JSONRPCException as e:
tx_info = None
# While it's quite unlikely, the transaction that was already in the blockchain could have been
# reorged while we were querying bitcoind to get the confirmation count. In such a case we just
# restart the tracker
# reorged while we were querying bitcoind to get the confirmation count. In that case we just restart
# the tracker
if e.error.get("code") == rpc_errors.RPC_INVALID_ADDRESS_OR_KEY:
logger.info("Transaction not found in mempool nor blockchain", txid=txid)
@@ -154,4 +153,4 @@ class Carrier:
# If something else happens (unlikely but possible) log it so we can treat it in future releases
logger.error("JSONRPCException", method="Carrier.get_transaction", error=e.error)
return tx_info
return None

View File

@@ -10,8 +10,8 @@ logger = Logger(actor="ChainMonitor", log_name_prefix=LOG_PREFIX)
class ChainMonitor:
"""
The :class:`ChainMonitor` is the class in charge of monitoring the blockchain (via ``bitcoind``) to detect new
blocks on top of the best chain. If a new best block is spotted, the chain monitor will notify the
The :class:`ChainMonitor` is in charge of monitoring the blockchain (via ``bitcoind``) to detect new blocks on top
of the best chain. If a new best block is spotted, the chain monitor will notify the
:obj:`Watcher <teos.watcher.Watcher>` and the :obj:`Responder <teos.responder.Responder>` using ``Queues``.
The :class:`ChainMonitor` monitors the chain using two methods: ``zmq`` and ``polling``. Blocks are only notified
@@ -34,7 +34,6 @@ class ChainMonitor:
watcher_queue (:obj:`Queue`): a queue to send new best tips to the :obj:`Watcher <teos.watcher.Watcher>`.
responder_queue (:obj:`Queue`): a queue to send new best tips to the
:obj:`Responder <teos.responder.Responder>`.
polling_delta (:obj:`int`): time between polls (in seconds).
max_block_window_size (:obj:`int`): max size of last_tips.
block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a blockProcessor instance.
@@ -75,7 +74,6 @@ class ChainMonitor:
Args:
block_hash (:obj:`str`): the new block hash to be sent to the subscribers.
block_hash (:obj:`str`): the new block hash to be sent to the subscribers.
"""
self.watcher_queue.put(block_hash)
@@ -90,7 +88,7 @@ class ChainMonitor:
block_hash (:obj:`block_hash`): the new best tip.
Returns:
(:obj:`bool`): ``True`` is the state was successfully updated, ``False`` otherwise.
:obj:`bool`: True is the state was successfully updated, False otherwise.
"""
if block_hash != self.best_tip and block_hash not in self.last_tips:

View File

@@ -7,7 +7,7 @@ logger = Logger(actor="Cleaner", log_name_prefix=LOG_PREFIX)
class Cleaner:
"""
The :class:`Cleaner` is the class in charge of removing expired/completed data from the tower.
The :class:`Cleaner` is in charge of removing expired/completed data from the tower.
Mutable objects (like dicts) are passed-by-reference in Python, so no return is needed for the Cleaner.
"""
@@ -15,15 +15,16 @@ class Cleaner:
@staticmethod
def delete_appointment_from_memory(uuid, appointments, locator_uuid_map):
"""
Deletes an appointment from memory (appointments and locator_uuid_map dictionaries). If the given appointment
does not share locator with any other, the map will completely removed, otherwise, the uuid will be removed from
the map.
Deletes an appointment from memory (``appointments`` and ``locator_uuid_map`` dictionaries). If the given
appointment does not share locator with any other, the map will completely removed, otherwise, the uuid will be
removed from the map.
Args:
uuid (:obj:`str`): the identifier of the appointment to be deleted.
appointments (:obj:`dict`): the appointments dictionary from where the appointment should be removed.
locator_uuid_map (:obj:`dict`): the locator:uuid map from where the appointment should also be removed.
"""
locator = appointments[uuid].get("locator")
# Delete the appointment
@@ -136,6 +137,7 @@ class Cleaner:
db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance
to interact with the database.
"""
locator_maps_to_update = {}
for uuid in completed_appointments:

View File

@@ -3,7 +3,7 @@ import plyvel
class DBManager:
"""
The :class:`DBManager` is the class in charge of interacting with a database (``LevelDB``).
The :class:`DBManager` is in charge of interacting with a database (``LevelDB``).
Keys and values are stored as bytes in the database but processed as strings by the manager.
Args:

View File

@@ -1,9 +1,9 @@
from common.tools import check_compressed_pk_format
from common.tools import is_compressed_pk
from common.cryptographer import Cryptographer
class NotEnoughSlots(ValueError):
"""Raise this when trying to subtract more slots than a user has available."""
"""Raise this when trying to subtract more slots than a user has available"""
def __init__(self, user_pk, requested_slots):
self.user_pk = user_pk
@@ -21,8 +21,8 @@ class IdentificationFailure(Exception):
class Gatekeeper:
"""
The Gatekeeper is in charge of managing the access to the tower. Only registered users are allowed to perform
actions.
The :class:`Gatekeeper` is in charge of managing the access to the tower. Only registered users are allowed to
perform actions.
Attributes:
registered_users (:obj:`dict`): a map of user_pk:appointment_slots.
@@ -44,7 +44,7 @@ class Gatekeeper:
:obj:`int`: the number of available slots in the user subscription.
"""
if not check_compressed_pk_format(user_pk):
if not is_compressed_pk(user_pk):
raise ValueError("provided public key does not match expected format (33-byte hex string)")
if user_pk not in self.registered_users:
@@ -58,17 +58,17 @@ class Gatekeeper:
def identify_user(self, message, signature):
"""
Checks if the provided user signature comes from a registered user.
Checks if a request comes from a registered user by ec-recovering their public key from a signed message.
Args:
message (:obj:`bytes`): byte representation of the original message from where the signature was generated.
signature (:obj:`str`): the user's signature (hex encoded).
signature (:obj:`str`): the user's signature (hex-encoded).
Returns:
:obj:`str`: a compressed key recovered from the signature and matching a registered user.
Raises:
:obj:`<teos.gatekeeper.IdentificationFailure>`: if the user cannot be identified.
:obj:`IdentificationFailure`: if the user cannot be identified.
"""
if isinstance(message, bytes) and isinstance(signature, str):
@@ -89,18 +89,16 @@ class Gatekeeper:
Args:
user_pk(:obj:`str`): the public key that identifies the user (33-bytes hex str).
n: the number of slots to fill.
n (:obj:`int`): the number of slots to fill.
Raises:
:obj:`<teos.gatekeeper.NotEnoughSlots>`: if the user subscription does not have enough slots.
:obj:`NotEnoughSlots`: if the user subscription does not have enough slots.
"""
# We are not making sure the value passed is a integer, but the value is computed by the API and rounded before
# passing it to the gatekeeper.
# DISCUSS: we may want to return a different exception if the user does not exist
if user_pk in self.registered_users and n <= self.registered_users.get(user_pk).get("available_slots"):
self.registered_users[user_pk]["available_slots"] -= n
self.user_db.store_user(user_pk, self.registered_users[user_pk])
else:
raise NotEnoughSlots(user_pk, n)
@@ -110,11 +108,10 @@ class Gatekeeper:
Args:
user_pk(:obj:`str`): the public key that identifies the user (33-bytes hex str).
n: the number of slots to free.
n (:obj:`int`): the number of slots to free.
"""
# We are not making sure the value passed is a integer, but the value is computed by the API and rounded before
# passing it to the gatekeeper.
# DISCUSS: if the user does not exist we may want to log or return an exception.
if user_pk in self.registered_users:
self.registered_users[user_pk]["available_slots"] += n
self.user_db.store_user(user_pk, self.registered_users[user_pk])

View File

@@ -2,6 +2,7 @@ import re
import common.cryptographer
from common.logger import Logger
from common.tools import is_locator
from common.constants import LOCATOR_LEN_HEX
from common.appointment import Appointment
@@ -50,11 +51,10 @@ class Inspector:
Returns:
:obj:`Appointment <teos.appointment.Appointment>`: An appointment initialized with the
provided data.
:obj:`Appointment <teos.appointment.Appointment>`: An appointment initialized with the provided data.
Raises:
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
:obj:`InspectionFailed`: if any of the fields is wrong.
"""
if appointment_data is None:
@@ -64,7 +64,7 @@ class Inspector:
block_height = self.block_processor.get_block_count()
if block_height is None:
raise InspectionFailed(errors.UNKNOWN_JSON_RPC_EXCEPTION, "Unexpected error occurred")
raise InspectionFailed(errors.UNKNOWN_JSON_RPC_EXCEPTION, "unexpected error occurred")
self.check_locator(appointment_data.get("locator"))
self.check_start_time(appointment_data.get("start_time"), block_height)
@@ -79,13 +79,13 @@ class Inspector:
"""
Checks if the provided ``locator`` is correct.
Locators must be 16-byte hex encoded strings.
Locators must be 16-byte hex-encoded strings.
Args:
locator (:obj:`str`): the locator to be checked.
Raises:
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
:obj:`InspectionFailed`: if any of the fields is wrong.
"""
if locator is None:
@@ -99,7 +99,7 @@ class Inspector:
elif len(locator) != LOCATOR_LEN_HEX:
raise InspectionFailed(errors.APPOINTMENT_WRONG_FIELD_SIZE, "wrong locator size ({})".format(len(locator)))
elif re.search(r"^[0-9A-Fa-f]+$", locator) is None:
elif not is_locator(locator):
raise InspectionFailed(errors.APPOINTMENT_WRONG_FIELD_FORMAT, "wrong locator format ({})".format(locator))
@staticmethod
@@ -114,12 +114,9 @@ class Inspector:
block_height (:obj:`int`): the chain height.
Raises:
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
:obj:`InspectionFailed`: if any of the fields is wrong.
"""
# 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).
if start_time is None:
raise InspectionFailed(errors.APPOINTMENT_EMPTY_FIELD, "empty start_time received")
@@ -156,7 +153,7 @@ class Inspector:
block_height (:obj:`int`): the chain height.
Raises:
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
:obj:`InspectionFailed`: if any of the fields is wrong.
"""
# TODO: What's too close to the current height is not properly defined. Right now any appointment that ends in
@@ -193,11 +190,11 @@ class Inspector:
To self delays must be greater or equal to ``MIN_TO_SELF_DELAY``.
Args:
to_self_delay (:obj:`int`): The ``to_self_delay`` encoded in the ``csv`` of the ``htlc`` that this
appointment is covering.
to_self_delay (:obj:`int`): The ``to_self_delay`` encoded in the ``csv`` of ``to_remote`` output of the
commitment transaction this appointment is covering.
Raises:
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
:obj:`InspectionFailed`: if any of the fields is wrong.
"""
if to_self_delay is None:
@@ -229,10 +226,10 @@ class Inspector:
Checks if the provided ``encrypted_blob`` may be correct.
Args:
encrypted_blob (:obj:`str`): the encrypted blob to be checked (hex encoded).
encrypted_blob (:obj:`str`): the encrypted blob to be checked (hex-encoded).
Raises:
:obj:`InspectionFailed <teos.inspector.InspectionFailed>`: if any of the fields is wrong.
:obj:`InspectionFailed`: if any of the fields is wrong.
"""
if encrypted_blob is None:

View File

@@ -53,7 +53,7 @@ class TransactionTracker:
:obj:`TransactionTracker`: A ``TransactionTracker`` instantiated with the provided data.
Raises:
ValueError: if any of the required fields is missing.
:obj:`ValueError`: if any of the required fields is missing.
"""
locator = tx_tracker_data.get("locator")
@@ -72,7 +72,7 @@ class TransactionTracker:
def to_dict(self):
"""
Exports a :obj:`TransactionTracker` as a dictionary.
Encodes a :obj:`TransactionTracker` as a dictionary.
Returns:
:obj:`dict`: A dictionary containing the :obj:`TransactionTracker` data.
@@ -91,13 +91,16 @@ class TransactionTracker:
class Responder:
"""
The :class:`Responder` is the class in charge of ensuring that channel breaches are dealt with. It does so handling
The :class:`Responder` is in charge of ensuring that channel breaches are dealt with. It does so handling
the decrypted ``penalty_txs`` handed by the :obj:`Watcher <teos.watcher.Watcher>` and ensuring the they make it to
the blockchain.
Args:
db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance
to interact with the database.
carrier (:obj:`Carrier <teos.carrier.Carrier>`): a ``Carrier`` instance to send transactions to bitcoind.
block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a ``BlockProcessor`` instance to
get data from bitcoind.
Attributes:
trackers (:obj:`dict`): A dictionary containing the minimum information about the :obj:`TransactionTracker`
@@ -116,7 +119,6 @@ class Responder:
block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a ``BlockProcessor`` instance to
get data from bitcoind.
last_known_block (:obj:`str`): the last block known by the ``Responder``.
"""
def __init__(self, db_manager, carrier, block_processor):
@@ -131,6 +133,7 @@ class Responder:
self.last_known_block = db_manager.load_last_block_hash_responder()
def awake(self):
"""Starts a new thread to monitor the blockchain to make sure triggered appointments get enough depth"""
responder_thread = Thread(target=self.do_watch, daemon=True)
responder_thread.start()
@@ -205,9 +208,8 @@ class Responder:
"""
Creates a :obj:`TransactionTracker` after successfully broadcasting a ``penalty_tx``.
A reduction of :obj:`TransactionTracker` is stored in ``trackers`` and ``tx_tracker_map`` and the
``penalty_txid`` added to ``unconfirmed_txs`` if ``confirmations=0``. Finally, all the data is stored in the
database.
A summary of :obj:`TransactionTracker` is stored in ``trackers`` and ``tx_tracker_map`` and the ``penalty_txid``
added to ``unconfirmed_txs`` if ``confirmations=0``. Finally, all the data is stored in the database.
Args:
uuid (:obj:`str`): a unique identifier for the appointment.
@@ -248,7 +250,7 @@ class Responder:
def do_watch(self):
"""
Monitors the blockchain whilst there are pending trackers.
Monitors the blockchain for reorgs and appointment ends.
This is the main method of the :obj:`Responder` and triggers tracker cleaning, rebroadcasting, reorg managing,
etc.
@@ -384,9 +386,9 @@ class Responder:
def rebroadcast(self, txs_to_rebroadcast):
"""
Rebroadcasts a ``penalty_tx`` that has missed too many confirmations. In the current approach this would loop
forever si the transaction keeps not getting it.
forever if the transaction keeps not getting it.
Potentially the fees could be bumped here if the transaction has some tower dedicated outputs (or allows it
Potentially, the fees could be bumped here if the transaction has some tower dedicated outputs (or allows it
trough ``ANYONECANPAY`` or something similar).
Args:

View File

@@ -45,9 +45,10 @@ def main(command_line_conf):
signal(SIGQUIT, handle_signals)
# Loads config and sets up the data folder and log file
config_loader = ConfigLoader(DATA_DIR, CONF_FILE_NAME, DEFAULT_CONF, command_line_conf)
data_dir = command_line_conf.get("DATA_DIR") if "DATA_DIR" in command_line_conf else DATA_DIR
config_loader = ConfigLoader(data_dir, CONF_FILE_NAME, DEFAULT_CONF, command_line_conf)
config = config_loader.build_config()
setup_data_folder(DATA_DIR)
setup_data_folder(data_dir)
setup_logging(config.get("LOG_FILE"), LOG_PREFIX)
logger.info("Starting TEOS")
@@ -183,7 +184,7 @@ if __name__ == "__main__":
except ValueError:
exit("btcrpcport must be an integer")
if opt in ["--datadir"]:
DATA_DIR = os.path.expanduser(arg)
command_line_conf["DATA_DIR"] = os.path.expanduser(arg)
if opt in ["-h", "--help"]:
exit(show_usage())

View File

@@ -15,7 +15,7 @@ def bitcoin_cli(btc_connect_params):
Args:
btc_connect_params (:obj:`dict`): a dictionary with the parameters to connect to bitcoind
(rpc user, rpc passwd, host and port)
(rpc user, rpc password, host and port)
Returns:
:obj:`AuthServiceProxy <teos.utils.auth_proxy.AuthServiceProxy>`: An authenticated service proxy to ``bitcoind``
@@ -40,7 +40,7 @@ def can_connect_to_bitcoind(btc_connect_params):
Args:
btc_connect_params (:obj:`dict`): a dictionary with the parameters to connect to bitcoind
(rpc user, rpc passwd, host and port)
(rpc user, rpc password, host and port)
Returns:
:obj:`bool`: ``True`` if the connection can be established. ``False`` otherwise.
"""
@@ -62,7 +62,7 @@ def in_correct_network(btc_connect_params, network):
Args:
btc_connect_params (:obj:`dict`): a dictionary with the parameters to connect to bitcoind
(rpc user, rpc passwd, host and port)
(rpc user, rpc password, host and port)
network (:obj:`str`): the network the tower is connected to.
Returns:

View File

@@ -5,14 +5,14 @@ from teos import LOG_PREFIX
from teos.db_manager import DBManager
from common.logger import Logger
from common.tools import check_compressed_pk_format
from common.tools import is_compressed_pk
logger = Logger(actor="UsersDBM", log_name_prefix=LOG_PREFIX)
class UsersDBM(DBManager):
"""
The :class:`UsersDBM` is the class in charge of interacting with the users database (``LevelDB``).
The :class:`UsersDBM` is in charge of interacting with the users database (``LevelDB``).
Keys and values are stored as bytes in the database but processed as strings by the manager.
Args:
@@ -20,8 +20,8 @@ class UsersDBM(DBManager):
database will be create if the specified path does not contain one.
Raises:
ValueError: If the provided ``db_path`` is not a string.
plyvel.Error: If the db is currently unavailable (being used by another process).
:obj:`ValueError`: If the provided ``db_path`` is not a string.
:obj:`plyvel.Error`: If the db is currently unavailable (being used by another process).
"""
def __init__(self, db_path):
@@ -46,31 +46,33 @@ class UsersDBM(DBManager):
user_data (:obj:`dict`): the user associated data, as a dictionary.
Returns:
:obj:`bool`: True if the user was stored in the database, false otherwise.
:obj:`bool`: True if the user was stored in the database, False otherwise.
"""
if check_compressed_pk_format(user_pk):
if is_compressed_pk(user_pk):
try:
self.create_entry(user_pk, json.dumps(user_data))
logger.info("Adding user to Gatekeeper's db", user_pk=user_pk)
return True
except json.JSONDecodeError:
logger.info("Could't add user to db. Wrong user data format.", user_pk=user_pk, user_data=user_data)
logger.info("Could't add user to db. Wrong user data format", user_pk=user_pk, user_data=user_data)
return False
except TypeError:
logger.info("Could't add user to db.", user_pk=user_pk, user_data=user_data)
logger.info("Could't add user to db", user_pk=user_pk, user_data=user_data)
return False
else:
logger.info("Could't add user to db. Wrong pk format.", user_pk=user_pk, user_data=user_data)
logger.info("Could't add user to db. Wrong pk format", user_pk=user_pk, user_data=user_data)
return False
def load_user(self, user_pk):
"""
Loads a user record from the database using the ``user_pk`` as identifier.
use_pk (:obj:`str`): a 33-byte hex-encoded string identifying the user.
Args:
user_pk (:obj:`str`): a 33-byte hex-encoded string identifying the user.
Returns:
:obj:`dict`: A dictionary containing the appointment data if they ``key`` is found.

View File

@@ -16,8 +16,7 @@ common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_
class Watcher:
"""
The :class:`Watcher` is the class in charge to watch for channel breaches for the appointments accepted by the
tower.
The :class:`Watcher` is in charge of watching for channel breaches for the appointments accepted by the tower.
The :class:`Watcher` keeps track of the accepted appointments in ``appointments`` and, for new received block,
checks if any breach has happened by comparing the txids with the appointment locators. If a breach is seen, the
@@ -36,7 +35,7 @@ class Watcher:
get block from bitcoind.
responder (:obj:`Responder <teos.responder.Responder>`): a ``Responder`` instance.
sk_der (:obj:`bytes`): a DER encoded private key used to sign appointment receipts (signaling acceptance).
max_appointments (:obj:`int`): the maximum ammount of appointments accepted by the ``Watcher`` at the same time.
max_appointments (:obj:`int`): the maximum amount of appointments accepted by the ``Watcher`` at the same time.
expiry_delta (:obj:`int`): the additional time the ``Watcher`` will keep an expired appointment around.
Attributes:
@@ -53,7 +52,7 @@ class Watcher:
get block from bitcoind.
responder (:obj:`Responder <teos.responder.Responder>`): a ``Responder`` instance.
signing_key (:mod:`PrivateKey`): a private key used to sign accepted appointments.
max_appointments (:obj:`int`): the maximum ammount of appointments accepted by the ``Watcher`` at the same time.
max_appointments (:obj:`int`): the maximum amount of appointments accepted by the ``Watcher`` at the same time.
expiry_delta (:obj:`int`): the additional time the ``Watcher`` will keep an expired appointment around.
Raises:
@@ -73,9 +72,7 @@ class Watcher:
self.signing_key = Cryptographer.load_private_key_der(sk_der)
def awake(self):
"""
Starts a new thread to monitor the blockchain for channel breaches.
"""
"""Starts a new thread to monitor the blockchain for channel breaches"""
watcher_thread = Thread(target=self.do_watch, daemon=True)
watcher_thread.start()
@@ -85,13 +82,13 @@ class Watcher:
def get_appointment_summary(self, uuid):
"""
Returns the summary of an appointment. The summary consists of the data kept in memory:
locator, end_time, and size.
{locator, end_time, and size}
Args:
uuid (:obj:`str`): a 16-byte hex string identifying the appointment.
Returns:
:obj:`dict` or :obj:`None`: a dictionary with the appointment summary, or None if the appointment is not
:obj:`dict` or :obj:`None`: a dictionary with the appointment summary, or ``None`` if the appointment is not
found.
"""
return self.appointments.get(uuid)
@@ -100,8 +97,8 @@ class Watcher:
"""
Adds a new appointment to the ``appointments`` dictionary if ``max_appointments`` has not been reached.
``add_appointment`` is the entry point of the Watcher. Upon receiving a new appointment it will start monitoring
the blockchain (``do_watch``) until ``appointments`` is empty.
``add_appointment`` is the entry point of the ``Watcher``. Upon receiving a new appointment it will start
monitoring the blockchain (``do_watch``) until ``appointments`` is empty.
Once a breach is seen on the blockchain, the :obj:`Watcher` will decrypt the corresponding
:obj:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>` and pass the information to the
@@ -123,7 +120,6 @@ class Watcher:
- ``(True, signature)`` if the appointment has been accepted.
- ``(False, None)`` otherwise.
"""
if len(self.appointments) < self.max_appointments:
@@ -164,7 +160,7 @@ class Watcher:
def do_watch(self):
"""
Monitors the blockchain whilst there are pending appointments.
Monitors the blockchain for channel breaches.
This is the main method of the :obj:`Watcher` and the one in charge to pass appointments to the
:obj:`Responder <teos.responder.Responder>` upon detecting a breach.

View File

@@ -3,9 +3,9 @@ import logging
from common.constants import LOCATOR_LEN_BYTES
from common.tools import (
check_compressed_pk_format,
check_sha256_hex_format,
check_locator_format,
is_compressed_pk,
is_256b_hex_str,
is_locator,
compute_locator,
setup_data_folder,
setup_logging,
@@ -13,7 +13,7 @@ from common.tools import (
from test.common.unit.conftest import get_random_value_hex
def test_check_compressed_pk_format():
def test_is_compressed_pk():
wrong_values = [
None,
3,
@@ -34,21 +34,21 @@ def test_check_compressed_pk_format():
prefix = "02"
else:
prefix = "03"
assert check_compressed_pk_format(prefix + get_random_value_hex(32))
assert is_compressed_pk(prefix + get_random_value_hex(32))
# check_user_pk must only accept values that is not a 33-byte hex string
for value in wrong_values:
assert not check_compressed_pk_format(value)
assert not is_compressed_pk(value)
def test_check_sha256_hex_format():
def test_is_256b_hex_str():
# Only 32-byte hex encoded strings should pass the test
wrong_inputs = [None, str(), 213, 46.67, dict(), "A" * 63, "C" * 65, bytes(), get_random_value_hex(31)]
for wtype in wrong_inputs:
assert check_sha256_hex_format(wtype) is False
assert is_256b_hex_str(wtype) is False
for v in range(100):
assert check_sha256_hex_format(get_random_value_hex(32)) is True
assert is_256b_hex_str(get_random_value_hex(32)) is True
def test_check_locator_format():
@@ -66,20 +66,20 @@ def test_check_locator_format():
get_random_value_hex(LOCATOR_LEN_BYTES - 1),
]
for wtype in wrong_inputs:
assert check_locator_format(wtype) is False
assert is_locator(wtype) is False
for _ in range(100):
assert check_locator_format(get_random_value_hex(LOCATOR_LEN_BYTES)) is True
assert is_locator(get_random_value_hex(LOCATOR_LEN_BYTES)) is True
def test_compute_locator():
# The best way of checking that compute locator is correct is by using check_locator_format
# The best way of checking that compute locator is correct is by using is_locator
for _ in range(100):
assert check_locator_format(compute_locator(get_random_value_hex(LOCATOR_LEN_BYTES))) is True
assert is_locator(compute_locator(get_random_value_hex(LOCATOR_LEN_BYTES))) is True
# String of length smaller than LOCATOR_LEN_BYTES bytes must fail
for i in range(1, LOCATOR_LEN_BYTES):
assert check_locator_format(compute_locator(get_random_value_hex(i))) is False
assert is_locator(compute_locator(get_random_value_hex(i))) is False
def test_setup_data_folder():

View File

@@ -1,5 +1,4 @@
from teos.tools import can_connect_to_bitcoind, in_correct_network, bitcoin_cli
from common.tools import check_sha256_hex_format
from test.teos.unit.conftest import bitcoind_connect_params
@@ -27,32 +26,3 @@ def test_bitcoin_cli():
except Exception:
assert False
def test_check_sha256_hex_format():
assert check_sha256_hex_format(None) is False
assert check_sha256_hex_format("") is False
assert (
check_sha256_hex_format(0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF) is False
) # wrong type
assert (
check_sha256_hex_format("abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd") is True
) # lowercase
assert (
check_sha256_hex_format("ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCD") is True
) # uppercase
assert (
check_sha256_hex_format("0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789ABCDEF") is True
) # mixed case
assert (
check_sha256_hex_format("0123456789012345678901234567890123456789012345678901234567890123") is True
) # only nums
assert (
check_sha256_hex_format("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdf") is False
) # too short
assert (
check_sha256_hex_format("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0") is False
) # too long
assert (
check_sha256_hex_format("g123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") is False
) # non-hex