mirror of
https://github.com/aljazceru/python-teos.git
synced 2025-12-18 14:44:21 +01:00
Fixes comments, docstrings and some renamings
This commit is contained in:
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
|
||||
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
|
||||
|
||||
@@ -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:
|
||||
@@ -168,7 +178,7 @@ class AppointmentsDBM(DBManager):
|
||||
|
||||
Args:
|
||||
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:
|
||||
:obj:`bool`: True if the appointment was stored in the db. False otherwise.
|
||||
@@ -193,7 +203,7 @@ class AppointmentsDBM(DBManager):
|
||||
|
||||
Args:
|
||||
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:
|
||||
:obj:`bool`: True if the tracker was stored in the db. False otherwise.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
@@ -161,7 +163,7 @@ class Cleaner:
|
||||
@staticmethod
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -13,7 +13,7 @@ logger = Logger(actor="Responder", log_name_prefix=LOG_PREFIX)
|
||||
|
||||
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
|
||||
passed along to the :obj:`Responder`.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -140,7 +143,7 @@ class Responder:
|
||||
"""
|
||||
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`
|
||||
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``.
|
||||
|
||||
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:
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user