mirror of
https://github.com/aljazceru/python-teos.git
synced 2025-12-17 14:14:22 +01:00
Fixes comments, docstrings and some renamings
This commit is contained in:
@@ -9,18 +9,17 @@ class Appointment:
|
|||||||
The :class:`Appointment` contains the information regarding an appointment between a client and the Watchtower.
|
The :class:`Appointment` contains the information regarding an appointment between a client and the Watchtower.
|
||||||
|
|
||||||
Args:
|
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.
|
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.
|
start_time (:obj:`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.
|
end_time (:obj:`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
|
to_self_delay (:obj:`int`): The ``to_self_delay`` encoded in the ``csv`` of the ``to_remote`` output of the
|
||||||
is covering.
|
commitment transaction that this appointment is covering.
|
||||||
encrypted_blob (:obj:`EncryptedBlob <common.encrypted_blob.EncryptedBlob>`): An ``EncryptedBlob`` object
|
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
|
containing an encrypted penalty transaction. The tower will decrypt it and broadcast the penalty transaction
|
||||||
upon seeing a breach on the blockchain.
|
upon seeing a breach on the blockchain.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# DISCUSS: 35-appointment-checks
|
|
||||||
def __init__(self, locator, start_time, end_time, to_self_delay, encrypted_blob):
|
def __init__(self, locator, start_time, end_time, to_self_delay, encrypted_blob):
|
||||||
self.locator = locator
|
self.locator = locator
|
||||||
self.start_time = start_time # ToDo: #4-standardize-appointment-fields
|
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.
|
This method is useful to load data from a database.
|
||||||
|
|
||||||
Args:
|
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}``
|
``{locator, start_time, end_time, to_self_delay, encrypted_blob}``
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -62,11 +61,10 @@ class Appointment:
|
|||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
"""
|
"""
|
||||||
Exports an appointment as a dictionary.
|
Encodes an appointment as a dictionary.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`dict`: A dictionary containing the appointment attributes.
|
:obj:`dict`: A dictionary containing the appointment attributes.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ToDO: #3-improve-appointment-structure
|
# ToDO: #3-improve-appointment-structure
|
||||||
@@ -90,7 +88,7 @@ class Appointment:
|
|||||||
All values are big endian.
|
All values are big endian.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:mod:`bytes`: The serialized data to be signed.
|
:obj:`bytes`: The serialized data to be signed.
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
unhexlify(self.locator)
|
unhexlify(self.locator)
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ class ConfigLoader:
|
|||||||
data_dir (:obj:`str`): the path to the data directory where the configuration file may be found.
|
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_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.
|
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
|
command_line_conf (:obj:`dict`): a dictionary containing the command line parameters that may replace the
|
||||||
ones in default / config file.
|
ones in default / config file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, data_dir, conf_file_name, default_conf, command_line_conf):
|
def __init__(self, data_dir, conf_file_name, default_conf, command_line_conf):
|
||||||
self.data_dir = data_dir
|
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.conf_fields = default_conf
|
||||||
self.command_line_conf = command_line_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.
|
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
|
- command line
|
||||||
- config file
|
- config file
|
||||||
- defaults
|
- defaults
|
||||||
|
|
||||||
Returns:
|
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 = configparser.ConfigParser()
|
||||||
file_config.read(self.conf_file_path)
|
file_config.read(self.conf_file_path)
|
||||||
|
|
||||||
|
# Load parameters and cast them to int if necessary
|
||||||
if file_config:
|
if file_config:
|
||||||
for sec in file_config.sections():
|
for sec in file_config.sections():
|
||||||
for k, v in file_config.items(sec):
|
for k, v in file_config.items(sec):
|
||||||
@@ -82,10 +83,10 @@ class ConfigLoader:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`dict`: A dictionary with the same keys as the provided one, but containing only the "value" field as
|
: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:
|
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 = {}
|
conf_dict = {}
|
||||||
@@ -104,11 +105,11 @@ class ConfigLoader:
|
|||||||
|
|
||||||
def extend_paths(self):
|
def extend_paths(self):
|
||||||
"""
|
"""
|
||||||
Extends the relative paths of the ``conf_fields`` dictionary with ``data_dir``.
|
Extends the relative paths of the ``conf_fields`` dictionary with ``data_dir``.
|
||||||
|
|
||||||
If an absolute path is given, it'll remain the same.
|
If an absolute path is given, it'll remain the same.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for key, field in self.conf_fields.items():
|
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"])
|
self.conf_fields[key]["value"] = os.path.join(self.data_dir, self.conf_fields[key]["value"])
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ from coincurve import PrivateKey, PublicKey
|
|||||||
from cryptography.exceptions import InvalidTag
|
from cryptography.exceptions import InvalidTag
|
||||||
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
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:"
|
LN_MESSAGE_PREFIX = b"Lightning Signed Message:"
|
||||||
|
|
||||||
|
|
||||||
def sha256d(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:
|
Args:
|
||||||
message(:obj:`bytes`): the message to be used as input to the hash function.
|
message(:obj:`bytes`): the message to be used as input to the hash function.
|
||||||
@@ -46,10 +46,9 @@ def hash_160(message):
|
|||||||
# NOTCOVERED
|
# NOTCOVERED
|
||||||
def sigrec_encode(rsig_rid):
|
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
|
``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.
|
messages we need it as the first. From: https://twitter.com/rusty_twit/status/1182102005914800128
|
||||||
From: https://twitter.com/rusty_twit/status/1182102005914800128
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
rsig_rid(:obj:`bytes`): the signature to be encoded.
|
rsig_rid(:obj:`bytes`): the signature to be encoded.
|
||||||
@@ -94,7 +93,7 @@ logger = None
|
|||||||
|
|
||||||
class Cryptographer:
|
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
|
@staticmethod
|
||||||
@@ -104,21 +103,21 @@ class Cryptographer:
|
|||||||
formatted.
|
formatted.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data(:mod:`str`): the data to be encrypted.
|
data(:obj:`str`): the data to be encrypted.
|
||||||
secret(:mod:`str`): the secret used to derive the encryption key.
|
secret(:obj:`str`): the secret used to derive the encryption key.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`bool`: Whether or not the ``key`` and ``data`` are properly formatted.
|
:obj:`bool`: Whether or not the ``key`` and ``data`` are properly formatted.
|
||||||
|
|
||||||
Raises:
|
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:
|
if len(data) % 2:
|
||||||
error = "Incorrect (Odd-length) value"
|
error = "Incorrect (Odd-length) value"
|
||||||
raise ValueError(error)
|
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)"
|
error = "Secret must be a 32-byte hex value (64 hex chars)"
|
||||||
raise ValueError(error)
|
raise ValueError(error)
|
||||||
|
|
||||||
@@ -127,16 +126,19 @@ class Cryptographer:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def encrypt(blob, secret):
|
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``.
|
``SHA256(secret)`` is used as ``key``, and ``0 (12-byte)`` as ``iv``.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
blob (:mod:`Blob <common.cli.blob.Blob>`): a ``Blob`` object containing a raw penalty transaction.
|
blob (:obj:`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.
|
secret (:obj:`str`): a value to used to derive the encryption key. Should be the dispute txid.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`str`: The encrypted data (hex encoded).
|
: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)
|
Cryptographer.check_data_key_format(blob.data, secret)
|
||||||
@@ -162,17 +164,20 @@ class Cryptographer:
|
|||||||
# ToDo: #20-test-tx-decrypting-edge-cases
|
# ToDo: #20-test-tx-decrypting-edge-cases
|
||||||
def decrypt(encrypted_blob, secret):
|
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``.
|
``SHA256(secret)`` is used as ``key``, and ``0 (12-byte)`` as ``iv``.
|
||||||
|
|
||||||
Args:
|
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.
|
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:
|
Returns:
|
||||||
:obj:`str`: The decrypted data (hex encoded).
|
: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)
|
Cryptographer.check_data_key_format(encrypted_blob.data, secret)
|
||||||
@@ -234,17 +239,14 @@ class Cryptographer:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def load_private_key_der(sk_der):
|
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:
|
Args:
|
||||||
sk_der(:mod:`str`): a private key encoded in ``DER`` format.
|
sk_der(:obj:`str`): a private key encoded in ``DER`` format.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:mod:`PrivateKey`: A ``PrivateKey`` object.
|
:obj:`PrivateKey` or :obj:`None`: A ``PrivateKey`` object. if the private key can be loaded. `None`
|
||||||
|
otherwise.
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
sk = PrivateKey.from_der(sk_der)
|
sk = PrivateKey.from_der(sk_der)
|
||||||
@@ -261,14 +263,14 @@ class Cryptographer:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def sign(message, sk):
|
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:
|
Args:
|
||||||
message(:obj:`bytes`): the data to be signed.
|
message(:obj:`bytes`): the data to be signed.
|
||||||
sk(:obj:`PrivateKey`): the ECDSA secret key used to signed the data.
|
sk(:obj:`PrivateKey`): the ECDSA secret key used to signed the data.
|
||||||
|
|
||||||
Returns:
|
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):
|
if not isinstance(message, bytes):
|
||||||
@@ -295,7 +297,7 @@ class Cryptographer:
|
|||||||
zb32_sig(:obj:`str`): the zbase32 signature of the message.
|
zb32_sig(:obj:`str`): the zbase32 signature of the message.
|
||||||
|
|
||||||
Returns:
|
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):
|
if not isinstance(message, bytes):
|
||||||
@@ -345,13 +347,14 @@ class Cryptographer:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_compressed_pk(pk):
|
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:
|
Args:
|
||||||
pk(:obj:`PublicKey`): a given public key.
|
pk(:obj:`PublicKey`): a given public key.
|
||||||
|
|
||||||
Returns:
|
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):
|
if not isinstance(pk, PublicKey):
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ class _StructuredMessage:
|
|||||||
|
|
||||||
class Logger:
|
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:
|
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``, ...).
|
actor (:obj:`str`): the system actor that is logging the event (e.g. ``Watcher``, ``Cryptographer``, ...).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ class Logger:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (:obj:`str`): the message to be logged.
|
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))
|
self.f_logger.info(self._create_file_message(msg, **kwargs))
|
||||||
@@ -64,7 +65,7 @@ class Logger:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (:obj:`str`): the message to be logged.
|
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))
|
self.f_logger.debug(self._create_file_message(msg, **kwargs))
|
||||||
@@ -76,7 +77,7 @@ class Logger:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (:obj:`str`): the message to be logged.
|
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))
|
self.f_logger.error(self._create_file_message(msg, **kwargs))
|
||||||
@@ -88,7 +89,7 @@ class Logger:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
msg (:obj:`str`): the message to be logged.
|
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))
|
self.f_logger.warning(self._create_file_message(msg, **kwargs))
|
||||||
|
|||||||
@@ -4,21 +4,21 @@ from pathlib import Path
|
|||||||
from common.constants import LOCATOR_LEN_HEX
|
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:
|
Args:
|
||||||
compressed_pk(:obj:`str`): the value to be checked.
|
value(:obj:`str`): the value to be checked.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`bool`: Whether or not the value matches the format.
|
: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.
|
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
|
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.
|
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.
|
Create a data folder for either the client or the server side if the folder does not exists.
|
||||||
|
|
||||||
Args:
|
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)
|
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):
|
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:
|
Args:
|
||||||
log_file_path (:obj:`str`): the path of the file to output the file log.
|
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):
|
if not isinstance(log_file_path, str):
|
||||||
print(log_file_path)
|
print(log_file_path)
|
||||||
raise ValueError("Wrong log file path.")
|
raise ValueError("Wrong log file path")
|
||||||
|
|
||||||
if not isinstance(log_name_prefix, str):
|
if not isinstance(log_name_prefix, str):
|
||||||
raise ValueError("Wrong log file name.")
|
raise ValueError("Wrong log file name")
|
||||||
|
|
||||||
# Create the file logger
|
# Create the file logger
|
||||||
f_logger = logging.getLogger("{}_file_log".format(log_name_prefix))
|
f_logger = logging.getLogger("{}_file_log".format(log_name_prefix))
|
||||||
|
|||||||
42
teos/api.py
42
teos/api.py
@@ -45,7 +45,7 @@ def get_remote_addr():
|
|||||||
# NOTCOVERED: not sure how to monkey path this one. May be related to #77
|
# NOTCOVERED: not sure how to monkey path this one. May be related to #77
|
||||||
def get_request_data_json(request):
|
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:
|
Args:
|
||||||
request (:obj:`Request`): the request sent by the user.
|
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.
|
:obj:`dict`: the dictionary parsed from the json request.
|
||||||
|
|
||||||
Raises:
|
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:
|
if request.is_json:
|
||||||
@@ -69,13 +69,14 @@ def get_request_data_json(request):
|
|||||||
|
|
||||||
class API:
|
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:
|
Args:
|
||||||
inspector (:obj:`Inspector <teos.inspector.Inspector>`): an ``Inspector`` instance to check the correctness of
|
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.
|
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):
|
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
|
Users register by sending a public key to the proper endpoint. This is exploitable atm, but will be solved when
|
||||||
payments are introduced.
|
payments are introduced.
|
||||||
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`tuple`: A tuple containing the response (``json``) and response code (``int``). For accepted requests,
|
:obj:`tuple`: A tuple containing the response (:obj:`str`) and response code (:obj:`int`). For accepted
|
||||||
the ``rcode`` is always 200 and the response contains a json with the public key and number of slots in the
|
requests, the ``rcode`` is always 200 and the response contains a json with the public key and number of
|
||||||
subscription. For rejected requests, the ``rcode`` is a 404 and the value contains an application specific
|
slots in the subscription. For rejected requests, the ``rcode`` is a 404 and the value contains an
|
||||||
error, and an error message. Error messages can be found at :mod:`Errors <teos.errors>`.
|
application error, and an error message. Error messages can be found at :mod:`Errors <teos.errors>`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
remote_addr = get_remote_addr()
|
remote_addr = get_remote_addr()
|
||||||
@@ -150,12 +150,12 @@ class API:
|
|||||||
Main endpoint of the Watchtower.
|
Main endpoint of the Watchtower.
|
||||||
|
|
||||||
The client sends requests (appointments) to this endpoint to request a job to the Watchtower. Requests must be
|
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:
|
Returns:
|
||||||
:obj:`tuple`: A tuple containing the response (``json``) and response code (``int``). For accepted
|
: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. For rejected
|
appointments, the ``rcode`` is always 200 and the response contains the receipt signature (json). For
|
||||||
appointments, the ``rcode`` is a 404 and the value contains an application specific error, and an error
|
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>`.
|
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_uuid = hash_160("{}{}".format(appointment.locator, user_pk))
|
||||||
appointment_summary = self.watcher.get_appointment_summary(appointment_uuid)
|
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:
|
if appointment_summary:
|
||||||
used_slots = ceil(appointment_summary.get("size") / ENCRYPTED_BLOB_MAX_SIZE_HEX)
|
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)
|
required_slots = ceil(len(appointment.encrypted_blob.data) / ENCRYPTED_BLOB_MAX_SIZE_HEX)
|
||||||
slot_diff = required_slots - used_slots
|
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
|
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:
|
else:
|
||||||
|
# For regular appointments 1 slot is reserved per ENCRYPTED_BLOB_MAX_SIZE_HEX block.
|
||||||
slot_diff = 0
|
slot_diff = 0
|
||||||
required_slots = ceil(len(appointment.encrypted_blob.data) / ENCRYPTED_BLOB_MAX_SIZE_HEX)
|
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``.
|
The information is requested by ``locator``.
|
||||||
|
|
||||||
Returns:
|
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
|
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.
|
: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.
|
This endpoint should only be accessible by the administrator. Requests are only allowed from localhost.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`dict`: A json formatted dictionary containing all the appointments hold by the
|
:obj:`str`: A json formatted dictionary containing all the appointments hold by the ``Watcher``
|
||||||
:obj:`Watcher <teos.watcher.Watcher>` (``watcher_appointments``) and by the
|
(``watcher_appointments``) and by the ``Responder>`` (``responder_trackers``).
|
||||||
:obj:`Responder <teos.responder.Responder>` (``responder_trackers``).
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ToDo: #15-add-system-monitor
|
# ToDo: #15-add-system-monitor
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ TRIGGERED_APPOINTMENTS_PREFIX = "ta"
|
|||||||
|
|
||||||
class AppointmentsDBM(DBManager):
|
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.
|
Keys and values are stored as bytes in the database but processed as strings by the manager.
|
||||||
|
|
||||||
The database is split in six prefixes:
|
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.
|
database will be create if the specified path does not contain one.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the provided ``db_path`` is not a string.
|
:obj:`ValueError`: If the provided ``db_path`` is not a string.
|
||||||
plyvel.Error: If the db is currently unavailable (being used by another process).
|
:obj:`plyvel.Error`: If the db is currently unavailable (being used by another process).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, db_path):
|
def __init__(self, db_path):
|
||||||
@@ -78,7 +78,11 @@ class AppointmentsDBM(DBManager):
|
|||||||
|
|
||||||
def get_last_known_block(self, key):
|
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:
|
Returns:
|
||||||
:obj:`str` or :obj:`None`: A 16-byte hex-encoded str representing the last known block hash.
|
: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
|
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:
|
Returns:
|
||||||
:obj:`dict`: A dictionary containing the appointment data if they ``key`` is found.
|
:obj:`dict`: A dictionary containing the appointment data if they ``key`` is found.
|
||||||
@@ -104,16 +111,19 @@ class AppointmentsDBM(DBManager):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = self.load_entry(key, prefix=WATCHER_PREFIX)
|
data = self.load_entry(uuid, prefix=WATCHER_PREFIX)
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
except (TypeError, json.decoder.JSONDecodeError):
|
except (TypeError, json.decoder.JSONDecodeError):
|
||||||
data = None
|
data = None
|
||||||
|
|
||||||
return data
|
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:
|
Returns:
|
||||||
:obj:`dict`: A dictionary containing the tracker data if they ``key`` is found.
|
:obj:`dict`: A dictionary containing the tracker data if they ``key`` is found.
|
||||||
@@ -122,7 +132,7 @@ class AppointmentsDBM(DBManager):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = self.load_entry(key, prefix=RESPONDER_PREFIX)
|
data = self.load_entry(uuid, prefix=RESPONDER_PREFIX)
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
except (TypeError, json.decoder.JSONDecodeError):
|
except (TypeError, json.decoder.JSONDecodeError):
|
||||||
data = None
|
data = None
|
||||||
@@ -134,7 +144,7 @@ class AppointmentsDBM(DBManager):
|
|||||||
Loads all the appointments from the database (all entries with the ``WATCHER_PREFIX`` prefix).
|
Loads all the appointments from the database (all entries with the ``WATCHER_PREFIX`` prefix).
|
||||||
|
|
||||||
Args:
|
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.
|
by default.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -168,7 +178,7 @@ class AppointmentsDBM(DBManager):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
uuid (:obj:`str`): the identifier of the appointment to be stored.
|
uuid (:obj:`str`): the identifier of the appointment to be stored.
|
||||||
appointment (:obj: `dict`): an appointment encoded as dictionary.
|
appointment (:obj:`dict`): an appointment encoded as dictionary.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`bool`: True if the appointment was stored in the db. False otherwise.
|
:obj:`bool`: True if the appointment was stored in the db. False otherwise.
|
||||||
@@ -193,7 +203,7 @@ class AppointmentsDBM(DBManager):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
uuid (:obj:`str`): the identifier of the appointment to be stored.
|
uuid (:obj:`str`): the identifier of the appointment to be stored.
|
||||||
tracker (:obj: `dict`): a tracker encoded as dictionary.
|
tracker (:obj:`dict`): a tracker encoded as dictionary.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`bool`: True if the tracker was stored in the db. False otherwise.
|
:obj:`bool`: True if the tracker was stored in the db. False otherwise.
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ class Receipt:
|
|||||||
|
|
||||||
class Carrier:
|
class Carrier:
|
||||||
"""
|
"""
|
||||||
The :class:`Carrier` is the class in charge of interacting with ``bitcoind`` to send/get transactions. It uses
|
The :class:`Carrier` is in charge of interacting with ``bitcoind`` to send/get transactions. It uses :obj:`Receipt`
|
||||||
:obj:`Receipt` objects to report about the sending outcome.
|
objects to report about the sending outcome.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
btc_connect_params (:obj:`dict`): a dictionary with the parameters to connect to bitcoind
|
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:
|
Attributes:
|
||||||
issued_receipts (:obj:`dict`): a dictionary of issued receipts to prevent resending the same transaction over
|
issued_receipts (:obj:`dict`): a dictionary of issued receipts to prevent resending the same transaction over
|
||||||
@@ -135,18 +135,17 @@ class Carrier:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`dict` or :obj:`None`: A dictionary with the transaction data if the transaction can be found on the
|
:obj:`dict` or :obj:`None`: A dictionary with the transaction data if the transaction can be found on the
|
||||||
chain.
|
chain. ``None`` otherwise.
|
||||||
Returns ``None`` otherwise.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tx_info = bitcoin_cli(self.btc_connect_params).getrawtransaction(txid, 1)
|
tx_info = bitcoin_cli(self.btc_connect_params).getrawtransaction(txid, 1)
|
||||||
|
return tx_info
|
||||||
|
|
||||||
except JSONRPCException as e:
|
except JSONRPCException as e:
|
||||||
tx_info = None
|
|
||||||
# While it's quite unlikely, the transaction that was already in the blockchain could have been
|
# 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
|
# reorged while we were querying bitcoind to get the confirmation count. In that case we just restart
|
||||||
# restart the tracker
|
# the tracker
|
||||||
if e.error.get("code") == rpc_errors.RPC_INVALID_ADDRESS_OR_KEY:
|
if e.error.get("code") == rpc_errors.RPC_INVALID_ADDRESS_OR_KEY:
|
||||||
logger.info("Transaction not found in mempool nor blockchain", txid=txid)
|
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
|
# 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)
|
logger.error("JSONRPCException", method="Carrier.get_transaction", error=e.error)
|
||||||
|
|
||||||
return tx_info
|
return None
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ logger = Logger(actor="ChainMonitor", log_name_prefix=LOG_PREFIX)
|
|||||||
|
|
||||||
class ChainMonitor:
|
class ChainMonitor:
|
||||||
"""
|
"""
|
||||||
The :class:`ChainMonitor` is the class in charge of monitoring the blockchain (via ``bitcoind``) to detect new
|
The :class:`ChainMonitor` is in charge of monitoring the blockchain (via ``bitcoind``) to detect new blocks on top
|
||||||
blocks on top of the best chain. If a new best block is spotted, the chain monitor will notify the
|
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``.
|
: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
|
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>`.
|
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
|
responder_queue (:obj:`Queue`): a queue to send new best tips to the
|
||||||
:obj:`Responder <teos.responder.Responder>`.
|
:obj:`Responder <teos.responder.Responder>`.
|
||||||
|
|
||||||
polling_delta (:obj:`int`): time between polls (in seconds).
|
polling_delta (:obj:`int`): time between polls (in seconds).
|
||||||
max_block_window_size (:obj:`int`): max size of last_tips.
|
max_block_window_size (:obj:`int`): max size of last_tips.
|
||||||
block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a blockProcessor instance.
|
block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a blockProcessor instance.
|
||||||
@@ -75,7 +74,6 @@ class ChainMonitor:
|
|||||||
|
|
||||||
Args:
|
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.
|
||||||
block_hash (:obj:`str`): the new block hash to be sent to the subscribers.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.watcher_queue.put(block_hash)
|
self.watcher_queue.put(block_hash)
|
||||||
@@ -90,7 +88,7 @@ class ChainMonitor:
|
|||||||
block_hash (:obj:`block_hash`): the new best tip.
|
block_hash (:obj:`block_hash`): the new best tip.
|
||||||
|
|
||||||
Returns:
|
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:
|
if block_hash != self.best_tip and block_hash not in self.last_tips:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ logger = Logger(actor="Cleaner", log_name_prefix=LOG_PREFIX)
|
|||||||
|
|
||||||
class Cleaner:
|
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.
|
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
|
@staticmethod
|
||||||
def delete_appointment_from_memory(uuid, appointments, locator_uuid_map):
|
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
|
Deletes an appointment from memory (``appointments`` and ``locator_uuid_map`` dictionaries). If the given
|
||||||
does not share locator with any other, the map will completely removed, otherwise, the uuid will be removed from
|
appointment does not share locator with any other, the map will completely removed, otherwise, the uuid will be
|
||||||
the map.
|
removed from the map.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
uuid (:obj:`str`): the identifier of the appointment to be deleted.
|
uuid (:obj:`str`): the identifier of the appointment to be deleted.
|
||||||
appointments (:obj:`dict`): the appointments dictionary from where the appointment should be removed.
|
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_uuid_map (:obj:`dict`): the locator:uuid map from where the appointment should also be removed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
locator = appointments[uuid].get("locator")
|
locator = appointments[uuid].get("locator")
|
||||||
|
|
||||||
# Delete the appointment
|
# Delete the appointment
|
||||||
@@ -136,6 +137,7 @@ class Cleaner:
|
|||||||
db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance
|
db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance
|
||||||
to interact with the database.
|
to interact with the database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
locator_maps_to_update = {}
|
locator_maps_to_update = {}
|
||||||
|
|
||||||
for uuid in completed_appointments:
|
for uuid in completed_appointments:
|
||||||
@@ -161,7 +163,7 @@ class Cleaner:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def flag_triggered_appointments(triggered_appointments, appointments, locator_uuid_map, db_manager):
|
def flag_triggered_appointments(triggered_appointments, appointments, locator_uuid_map, db_manager):
|
||||||
"""
|
"""
|
||||||
Deletes a list of triggered appointment from memory (:obj:`Watcher <teos.watcher.Watcher>`) and flags them as
|
Deletes a list of triggered appointment from memory (:obj:`Watcher <teos.watcher.Watcher>`) and flags them as
|
||||||
triggered on disk.
|
triggered on disk.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import plyvel
|
|||||||
|
|
||||||
class DBManager:
|
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.
|
Keys and values are stored as bytes in the database but processed as strings by the manager.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from common.tools import check_compressed_pk_format
|
from common.tools import is_compressed_pk
|
||||||
from common.cryptographer import Cryptographer
|
from common.cryptographer import Cryptographer
|
||||||
|
|
||||||
|
|
||||||
class NotEnoughSlots(ValueError):
|
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):
|
def __init__(self, user_pk, requested_slots):
|
||||||
self.user_pk = user_pk
|
self.user_pk = user_pk
|
||||||
@@ -21,8 +21,8 @@ class IdentificationFailure(Exception):
|
|||||||
|
|
||||||
class Gatekeeper:
|
class Gatekeeper:
|
||||||
"""
|
"""
|
||||||
The Gatekeeper is in charge of managing the access to the tower. Only registered users are allowed to perform
|
The :class:`Gatekeeper` is in charge of managing the access to the tower. Only registered users are allowed to
|
||||||
actions.
|
perform actions.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
registered_users (:obj:`dict`): a map of user_pk:appointment_slots.
|
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.
|
: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)")
|
raise ValueError("provided public key does not match expected format (33-byte hex string)")
|
||||||
|
|
||||||
if user_pk not in self.registered_users:
|
if user_pk not in self.registered_users:
|
||||||
@@ -58,17 +58,17 @@ class Gatekeeper:
|
|||||||
|
|
||||||
def identify_user(self, message, signature):
|
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:
|
Args:
|
||||||
message (:obj:`bytes`): byte representation of the original message from where the signature was generated.
|
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:
|
Returns:
|
||||||
:obj:`str`: a compressed key recovered from the signature and matching a registered user.
|
:obj:`str`: a compressed key recovered from the signature and matching a registered user.
|
||||||
|
|
||||||
Raises:
|
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):
|
if isinstance(message, bytes) and isinstance(signature, str):
|
||||||
@@ -89,18 +89,16 @@ class Gatekeeper:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
user_pk(:obj:`str`): the public key that identifies the user (33-bytes hex str).
|
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.
|
n (:obj:`int`): the number of slots to fill.
|
||||||
|
|
||||||
Raises:
|
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
|
# 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"):
|
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.registered_users[user_pk]["available_slots"] -= n
|
||||||
|
self.user_db.store_user(user_pk, self.registered_users[user_pk])
|
||||||
else:
|
else:
|
||||||
raise NotEnoughSlots(user_pk, n)
|
raise NotEnoughSlots(user_pk, n)
|
||||||
|
|
||||||
@@ -110,11 +108,10 @@ class Gatekeeper:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
user_pk(:obj:`str`): the public key that identifies the user (33-bytes hex str).
|
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.
|
# DISCUSS: if the user does not exist we may want to log or return an exception.
|
||||||
if user_pk in self.registered_users:
|
if user_pk in self.registered_users:
|
||||||
self.registered_users[user_pk]["available_slots"] += n
|
self.registered_users[user_pk]["available_slots"] += n
|
||||||
|
self.user_db.store_user(user_pk, self.registered_users[user_pk])
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import re
|
|||||||
|
|
||||||
import common.cryptographer
|
import common.cryptographer
|
||||||
from common.logger import Logger
|
from common.logger import Logger
|
||||||
|
from common.tools import is_locator
|
||||||
from common.constants import LOCATOR_LEN_HEX
|
from common.constants import LOCATOR_LEN_HEX
|
||||||
from common.appointment import Appointment
|
from common.appointment import Appointment
|
||||||
|
|
||||||
@@ -50,11 +51,10 @@ class Inspector:
|
|||||||
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`Appointment <teos.appointment.Appointment>`: An appointment initialized with the
|
:obj:`Appointment <teos.appointment.Appointment>`: An appointment initialized with the provided data.
|
||||||
provided data.
|
|
||||||
|
|
||||||
Raises:
|
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:
|
if appointment_data is None:
|
||||||
@@ -64,7 +64,7 @@ class Inspector:
|
|||||||
|
|
||||||
block_height = self.block_processor.get_block_count()
|
block_height = self.block_processor.get_block_count()
|
||||||
if block_height is None:
|
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_locator(appointment_data.get("locator"))
|
||||||
self.check_start_time(appointment_data.get("start_time"), block_height)
|
self.check_start_time(appointment_data.get("start_time"), block_height)
|
||||||
@@ -79,13 +79,13 @@ class Inspector:
|
|||||||
"""
|
"""
|
||||||
Checks if the provided ``locator`` is correct.
|
Checks if the provided ``locator`` is correct.
|
||||||
|
|
||||||
Locators must be 16-byte hex encoded strings.
|
Locators must be 16-byte hex-encoded strings.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
locator (:obj:`str`): the locator to be checked.
|
locator (:obj:`str`): the locator to be checked.
|
||||||
|
|
||||||
Raises:
|
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:
|
if locator is None:
|
||||||
@@ -99,7 +99,7 @@ class Inspector:
|
|||||||
elif len(locator) != LOCATOR_LEN_HEX:
|
elif len(locator) != LOCATOR_LEN_HEX:
|
||||||
raise InspectionFailed(errors.APPOINTMENT_WRONG_FIELD_SIZE, "wrong locator size ({})".format(len(locator)))
|
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))
|
raise InspectionFailed(errors.APPOINTMENT_WRONG_FIELD_FORMAT, "wrong locator format ({})".format(locator))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -114,12 +114,9 @@ class Inspector:
|
|||||||
block_height (:obj:`int`): the chain height.
|
block_height (:obj:`int`): the chain height.
|
||||||
|
|
||||||
Raises:
|
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:
|
if start_time is None:
|
||||||
raise InspectionFailed(errors.APPOINTMENT_EMPTY_FIELD, "empty start_time received")
|
raise InspectionFailed(errors.APPOINTMENT_EMPTY_FIELD, "empty start_time received")
|
||||||
|
|
||||||
@@ -156,7 +153,7 @@ class Inspector:
|
|||||||
block_height (:obj:`int`): the chain height.
|
block_height (:obj:`int`): the chain height.
|
||||||
|
|
||||||
Raises:
|
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
|
# 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``.
|
To self delays must be greater or equal to ``MIN_TO_SELF_DELAY``.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
to_self_delay (:obj:`int`): The ``to_self_delay`` encoded in the ``csv`` of the ``htlc`` that this
|
to_self_delay (:obj:`int`): The ``to_self_delay`` encoded in the ``csv`` of ``to_remote`` output of the
|
||||||
appointment is covering.
|
commitment transaction this appointment is covering.
|
||||||
|
|
||||||
Raises:
|
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:
|
if to_self_delay is None:
|
||||||
@@ -229,10 +226,10 @@ class Inspector:
|
|||||||
Checks if the provided ``encrypted_blob`` may be correct.
|
Checks if the provided ``encrypted_blob`` may be correct.
|
||||||
|
|
||||||
Args:
|
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:
|
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:
|
if encrypted_blob is None:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ logger = Logger(actor="Responder", log_name_prefix=LOG_PREFIX)
|
|||||||
|
|
||||||
class TransactionTracker:
|
class TransactionTracker:
|
||||||
"""
|
"""
|
||||||
A :class:`TransactionTracker` is used to monitor a ``penalty_tx``. Once the dispute is seen by the
|
A :class:`TransactionTracker` is used to monitor a ``penalty_tx``. Once the dispute is seen by the
|
||||||
:obj:`Watcher <teos.watcher.Watcher>` the penalty transaction is decrypted and the relevant appointment data is
|
:obj:`Watcher <teos.watcher.Watcher>` the penalty transaction is decrypted and the relevant appointment data is
|
||||||
passed along to the :obj:`Responder`.
|
passed along to the :obj:`Responder`.
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ class TransactionTracker:
|
|||||||
:obj:`TransactionTracker`: A ``TransactionTracker`` instantiated with the provided data.
|
:obj:`TransactionTracker`: A ``TransactionTracker`` instantiated with the provided data.
|
||||||
|
|
||||||
Raises:
|
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")
|
locator = tx_tracker_data.get("locator")
|
||||||
@@ -72,7 +72,7 @@ class TransactionTracker:
|
|||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
"""
|
"""
|
||||||
Exports a :obj:`TransactionTracker` as a dictionary.
|
Encodes a :obj:`TransactionTracker` as a dictionary.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`dict`: A dictionary containing the :obj:`TransactionTracker` data.
|
:obj:`dict`: A dictionary containing the :obj:`TransactionTracker` data.
|
||||||
@@ -91,13 +91,16 @@ class TransactionTracker:
|
|||||||
|
|
||||||
class Responder:
|
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 decrypted ``penalty_txs`` handed by the :obj:`Watcher <teos.watcher.Watcher>` and ensuring the they make it to
|
||||||
the blockchain.
|
the blockchain.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance
|
db_manager (:obj:`AppointmentsDBM <teos.appointments_dbm.AppointmentsDBM>`): a ``AppointmentsDBM`` instance
|
||||||
to interact with the database.
|
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:
|
Attributes:
|
||||||
trackers (:obj:`dict`): A dictionary containing the minimum information about the :obj:`TransactionTracker`
|
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
|
block_processor (:obj:`BlockProcessor <teos.block_processor.BlockProcessor>`): a ``BlockProcessor`` instance to
|
||||||
get data from bitcoind.
|
get data from bitcoind.
|
||||||
last_known_block (:obj:`str`): the last block known by the ``Responder``.
|
last_known_block (:obj:`str`): the last block known by the ``Responder``.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, db_manager, carrier, block_processor):
|
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()
|
self.last_known_block = db_manager.load_last_block_hash_responder()
|
||||||
|
|
||||||
def awake(self):
|
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 = Thread(target=self.do_watch, daemon=True)
|
||||||
responder_thread.start()
|
responder_thread.start()
|
||||||
|
|
||||||
@@ -140,7 +143,7 @@ class Responder:
|
|||||||
"""
|
"""
|
||||||
Whether the :obj:`Responder` is on sync with ``bitcoind`` or not. Used when recovering from a crash.
|
Whether the :obj:`Responder` is on sync with ``bitcoind`` or not. Used when recovering from a crash.
|
||||||
|
|
||||||
The Watchtower can be instantiated with fresh or with backed up data. In the later, some triggers may have been
|
The Watchtower can be instantiated with fresh or with backed up data. In the later, some triggers may have been
|
||||||
missed. In order to go back on sync both the :obj:`Watcher <teos.watcher.Watcher>` and the :obj:`Responder`
|
missed. In order to go back on sync both the :obj:`Watcher <teos.watcher.Watcher>` and the :obj:`Responder`
|
||||||
need to perform the state transitions until they catch up.
|
need to perform the state transitions until they catch up.
|
||||||
|
|
||||||
@@ -205,9 +208,8 @@ class Responder:
|
|||||||
"""
|
"""
|
||||||
Creates a :obj:`TransactionTracker` after successfully broadcasting a ``penalty_tx``.
|
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
|
A summary of :obj:`TransactionTracker` is stored in ``trackers`` and ``tx_tracker_map`` and the ``penalty_txid``
|
||||||
``penalty_txid`` added to ``unconfirmed_txs`` if ``confirmations=0``. Finally, all the data is stored in the
|
added to ``unconfirmed_txs`` if ``confirmations=0``. Finally, all the data is stored in the database.
|
||||||
database.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
uuid (:obj:`str`): a unique identifier for the appointment.
|
uuid (:obj:`str`): a unique identifier for the appointment.
|
||||||
@@ -248,7 +250,7 @@ class Responder:
|
|||||||
|
|
||||||
def do_watch(self):
|
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,
|
This is the main method of the :obj:`Responder` and triggers tracker cleaning, rebroadcasting, reorg managing,
|
||||||
etc.
|
etc.
|
||||||
@@ -384,9 +386,9 @@ class Responder:
|
|||||||
def rebroadcast(self, txs_to_rebroadcast):
|
def rebroadcast(self, txs_to_rebroadcast):
|
||||||
"""
|
"""
|
||||||
Rebroadcasts a ``penalty_tx`` that has missed too many confirmations. In the current approach this would loop
|
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).
|
trough ``ANYONECANPAY`` or something similar).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -45,9 +45,10 @@ def main(command_line_conf):
|
|||||||
signal(SIGQUIT, handle_signals)
|
signal(SIGQUIT, handle_signals)
|
||||||
|
|
||||||
# Loads config and sets up the data folder and log file
|
# 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()
|
config = config_loader.build_config()
|
||||||
setup_data_folder(DATA_DIR)
|
setup_data_folder(data_dir)
|
||||||
setup_logging(config.get("LOG_FILE"), LOG_PREFIX)
|
setup_logging(config.get("LOG_FILE"), LOG_PREFIX)
|
||||||
|
|
||||||
logger.info("Starting TEOS")
|
logger.info("Starting TEOS")
|
||||||
@@ -183,7 +184,7 @@ if __name__ == "__main__":
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
exit("btcrpcport must be an integer")
|
exit("btcrpcport must be an integer")
|
||||||
if opt in ["--datadir"]:
|
if opt in ["--datadir"]:
|
||||||
DATA_DIR = os.path.expanduser(arg)
|
command_line_conf["DATA_DIR"] = os.path.expanduser(arg)
|
||||||
if opt in ["-h", "--help"]:
|
if opt in ["-h", "--help"]:
|
||||||
exit(show_usage())
|
exit(show_usage())
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ def bitcoin_cli(btc_connect_params):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
btc_connect_params (:obj:`dict`): a dictionary with the parameters to connect to bitcoind
|
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:
|
Returns:
|
||||||
:obj:`AuthServiceProxy <teos.utils.auth_proxy.AuthServiceProxy>`: An authenticated service proxy to ``bitcoind``
|
: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:
|
Args:
|
||||||
btc_connect_params (:obj:`dict`): a dictionary with the parameters to connect to bitcoind
|
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:
|
Returns:
|
||||||
:obj:`bool`: ``True`` if the connection can be established. ``False`` otherwise.
|
:obj:`bool`: ``True`` if the connection can be established. ``False`` otherwise.
|
||||||
"""
|
"""
|
||||||
@@ -62,7 +62,7 @@ def in_correct_network(btc_connect_params, network):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
btc_connect_params (:obj:`dict`): a dictionary with the parameters to connect to bitcoind
|
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.
|
network (:obj:`str`): the network the tower is connected to.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ from teos import LOG_PREFIX
|
|||||||
from teos.db_manager import DBManager
|
from teos.db_manager import DBManager
|
||||||
|
|
||||||
from common.logger import Logger
|
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)
|
logger = Logger(actor="UsersDBM", log_name_prefix=LOG_PREFIX)
|
||||||
|
|
||||||
|
|
||||||
class UsersDBM(DBManager):
|
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.
|
Keys and values are stored as bytes in the database but processed as strings by the manager.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -20,8 +20,8 @@ class UsersDBM(DBManager):
|
|||||||
database will be create if the specified path does not contain one.
|
database will be create if the specified path does not contain one.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If the provided ``db_path`` is not a string.
|
:obj:`ValueError`: If the provided ``db_path`` is not a string.
|
||||||
plyvel.Error: If the db is currently unavailable (being used by another process).
|
:obj:`plyvel.Error`: If the db is currently unavailable (being used by another process).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, db_path):
|
def __init__(self, db_path):
|
||||||
@@ -46,31 +46,33 @@ class UsersDBM(DBManager):
|
|||||||
user_data (:obj:`dict`): the user associated data, as a dictionary.
|
user_data (:obj:`dict`): the user associated data, as a dictionary.
|
||||||
|
|
||||||
Returns:
|
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:
|
try:
|
||||||
self.create_entry(user_pk, json.dumps(user_data))
|
self.create_entry(user_pk, json.dumps(user_data))
|
||||||
logger.info("Adding user to Gatekeeper's db", user_pk=user_pk)
|
logger.info("Adding user to Gatekeeper's db", user_pk=user_pk)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
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
|
return False
|
||||||
|
|
||||||
except TypeError:
|
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
|
return False
|
||||||
else:
|
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
|
return False
|
||||||
|
|
||||||
def load_user(self, user_pk):
|
def load_user(self, user_pk):
|
||||||
"""
|
"""
|
||||||
Loads a user record from the database using the ``user_pk`` as identifier.
|
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:
|
Returns:
|
||||||
:obj:`dict`: A dictionary containing the appointment data if they ``key`` is found.
|
:obj:`dict`: A dictionary containing the appointment data if they ``key`` is found.
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=LOG_
|
|||||||
|
|
||||||
class Watcher:
|
class Watcher:
|
||||||
"""
|
"""
|
||||||
The :class:`Watcher` is the class in charge to watch for channel breaches for the appointments accepted by the
|
The :class:`Watcher` is in charge of watching for channel breaches for the appointments accepted by the tower.
|
||||||
tower.
|
|
||||||
|
|
||||||
The :class:`Watcher` keeps track of the accepted appointments in ``appointments`` and, for new received block,
|
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
|
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.
|
get block from bitcoind.
|
||||||
responder (:obj:`Responder <teos.responder.Responder>`): a ``Responder`` instance.
|
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).
|
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.
|
expiry_delta (:obj:`int`): the additional time the ``Watcher`` will keep an expired appointment around.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
@@ -53,7 +52,7 @@ class Watcher:
|
|||||||
get block from bitcoind.
|
get block from bitcoind.
|
||||||
responder (:obj:`Responder <teos.responder.Responder>`): a ``Responder`` instance.
|
responder (:obj:`Responder <teos.responder.Responder>`): a ``Responder`` instance.
|
||||||
signing_key (:mod:`PrivateKey`): a private key used to sign accepted appointments.
|
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.
|
expiry_delta (:obj:`int`): the additional time the ``Watcher`` will keep an expired appointment around.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
@@ -73,9 +72,7 @@ class Watcher:
|
|||||||
self.signing_key = Cryptographer.load_private_key_der(sk_der)
|
self.signing_key = Cryptographer.load_private_key_der(sk_der)
|
||||||
|
|
||||||
def awake(self):
|
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 = Thread(target=self.do_watch, daemon=True)
|
||||||
watcher_thread.start()
|
watcher_thread.start()
|
||||||
@@ -85,13 +82,13 @@ class Watcher:
|
|||||||
def get_appointment_summary(self, uuid):
|
def get_appointment_summary(self, uuid):
|
||||||
"""
|
"""
|
||||||
Returns the summary of an appointment. The summary consists of the data kept in memory:
|
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:
|
Args:
|
||||||
uuid (:obj:`str`): a 16-byte hex string identifying the appointment.
|
uuid (:obj:`str`): a 16-byte hex string identifying the appointment.
|
||||||
|
|
||||||
Returns:
|
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.
|
found.
|
||||||
"""
|
"""
|
||||||
return self.appointments.get(uuid)
|
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.
|
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
|
``add_appointment`` is the entry point of the ``Watcher``. Upon receiving a new appointment it will start
|
||||||
the blockchain (``do_watch``) until ``appointments`` is empty.
|
monitoring the blockchain (``do_watch``) until ``appointments`` is empty.
|
||||||
|
|
||||||
Once a breach is seen on the blockchain, the :obj:`Watcher` will decrypt the corresponding
|
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
|
: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.
|
- ``(True, signature)`` if the appointment has been accepted.
|
||||||
- ``(False, None)`` otherwise.
|
- ``(False, None)`` otherwise.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(self.appointments) < self.max_appointments:
|
if len(self.appointments) < self.max_appointments:
|
||||||
@@ -164,7 +160,7 @@ class Watcher:
|
|||||||
|
|
||||||
def do_watch(self):
|
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
|
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.
|
:obj:`Responder <teos.responder.Responder>` upon detecting a breach.
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import logging
|
|||||||
|
|
||||||
from common.constants import LOCATOR_LEN_BYTES
|
from common.constants import LOCATOR_LEN_BYTES
|
||||||
from common.tools import (
|
from common.tools import (
|
||||||
check_compressed_pk_format,
|
is_compressed_pk,
|
||||||
check_sha256_hex_format,
|
is_256b_hex_str,
|
||||||
check_locator_format,
|
is_locator,
|
||||||
compute_locator,
|
compute_locator,
|
||||||
setup_data_folder,
|
setup_data_folder,
|
||||||
setup_logging,
|
setup_logging,
|
||||||
@@ -13,7 +13,7 @@ from common.tools import (
|
|||||||
from test.common.unit.conftest import get_random_value_hex
|
from test.common.unit.conftest import get_random_value_hex
|
||||||
|
|
||||||
|
|
||||||
def test_check_compressed_pk_format():
|
def test_is_compressed_pk():
|
||||||
wrong_values = [
|
wrong_values = [
|
||||||
None,
|
None,
|
||||||
3,
|
3,
|
||||||
@@ -34,21 +34,21 @@ def test_check_compressed_pk_format():
|
|||||||
prefix = "02"
|
prefix = "02"
|
||||||
else:
|
else:
|
||||||
prefix = "03"
|
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
|
# check_user_pk must only accept values that is not a 33-byte hex string
|
||||||
for value in wrong_values:
|
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
|
# 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)]
|
wrong_inputs = [None, str(), 213, 46.67, dict(), "A" * 63, "C" * 65, bytes(), get_random_value_hex(31)]
|
||||||
for wtype in wrong_inputs:
|
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):
|
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():
|
def test_check_locator_format():
|
||||||
@@ -66,20 +66,20 @@ def test_check_locator_format():
|
|||||||
get_random_value_hex(LOCATOR_LEN_BYTES - 1),
|
get_random_value_hex(LOCATOR_LEN_BYTES - 1),
|
||||||
]
|
]
|
||||||
for wtype in wrong_inputs:
|
for wtype in wrong_inputs:
|
||||||
assert check_locator_format(wtype) is False
|
assert is_locator(wtype) is False
|
||||||
|
|
||||||
for _ in range(100):
|
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():
|
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):
|
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
|
# String of length smaller than LOCATOR_LEN_BYTES bytes must fail
|
||||||
for i in range(1, LOCATOR_LEN_BYTES):
|
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():
|
def test_setup_data_folder():
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from teos.tools import can_connect_to_bitcoind, in_correct_network, bitcoin_cli
|
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
|
from test.teos.unit.conftest import bitcoind_connect_params
|
||||||
|
|
||||||
|
|
||||||
@@ -27,32 +26,3 @@ def test_bitcoin_cli():
|
|||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
assert False
|
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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user