From c74f6a49af7a00077fc677549c1cacb954f6152f Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 3 Apr 2020 21:57:38 +0200 Subject: [PATCH 1/9] Refactors cli to avoid multi-type returns (normal return + None). Adds exceptions for errors. --- cli/exceptions.py | 22 +++ cli/teos_cli.py | 308 +++++++++++++++----------------- test/cli/unit/test_teos_cli.py | 86 +++++---- test/teos/e2e/test_basic_e2e.py | 110 ++++++------ 4 files changed, 271 insertions(+), 255 deletions(-) create mode 100644 cli/exceptions.py diff --git a/cli/exceptions.py b/cli/exceptions.py new file mode 100644 index 0000000..bb6f2f6 --- /dev/null +++ b/cli/exceptions.py @@ -0,0 +1,22 @@ +class InvalidParameter(ValueError): + """Raised when a command line parameter is invalid (either missing or wrong)""" + + def __init__(self, msg, **kwargs): + self.msg = msg + self.kwargs = kwargs + + +class InvalidKey(Exception): + """Raised when there is an error loading the keys""" + + def __init__(self, msg, **kwargs): + self.reason = msg + self.kwargs = kwargs + + +class TowerResponseError(Exception): + """Raised when the tower responds with an error""" + + def __init__(self, msg, **kwargs): + self.reason = msg + self.kwargs = kwargs diff --git a/cli/teos_cli.py b/cli/teos_cli.py index b53f8c3..c508379 100644 --- a/cli/teos_cli.py +++ b/cli/teos_cli.py @@ -10,8 +10,9 @@ from getopt import getopt, GetoptError from requests import ConnectTimeout, ConnectionError from requests.exceptions import MissingSchema, InvalidSchema, InvalidURL -from cli.help import show_usage, help_add_appointment, help_get_appointment, help_register from cli import DEFAULT_CONF, DATA_DIR, CONF_FILE_NAME, LOG_PREFIX +from cli.exceptions import InvalidKey, InvalidParameter, TowerResponseError +from cli.help import show_usage, help_add_appointment, help_get_appointment, help_register import common.cryptographer from common.blob import Blob @@ -36,26 +37,29 @@ def register(compressed_pk, teos_url): teos_url (:obj:`str`): the teos base url. Returns: - :obj:`dict` or :obj:`None`: a dictionary containing the tower response if the registration succeeded. ``None`` - otherwise. + :obj:`dict`: a dictionary containing the tower response if the registration succeeded. + + Raises: + :obj:`InvalidParameter `: if `compressed_pk` is invalid. + :obj:`ConnectionError`: if the client cannot connect to the tower. + :obj:`TowerResponseError `: if the tower responded with an error, or the + response was invalid. """ if not is_compressed_pk(compressed_pk): - logger.error("The cli public key is not valid") - return None + raise InvalidParameter("The cli public key is not valid") # Send request to the server. register_endpoint = "{}/register".format(teos_url) data = {"public_key": compressed_pk} logger.info("Registering in the Eye of Satoshi") - server_response = post_request(data, register_endpoint) - if server_response: - response_json = process_post_response(server_response) - return response_json + response = process_post_response(post_request(data, register_endpoint)) + + return response -def add_appointment(appointment_data, cli_sk, teos_pk, teos_url, appointments_folder_path): +def add_appointment(appointment_data, cli_sk, teos_pk, teos_url): """ Manages the add_appointment command. @@ -67,76 +71,67 @@ def add_appointment(appointment_data, cli_sk, teos_pk, teos_url, appointments_fo - Send the appointment to the tower - Wait for the response - Check the tower's response and signature - - Store the receipt (appointment + signature) on disk Args: appointment_data (:obj:`dict`): a dictionary containing the appointment data. cli_sk (:obj:`PrivateKey`): the client's private key. teos_pk (:obj:`PublicKey`): the tower's public key. teos_url (:obj:`str`): the teos base url. - appointments_folder_path (:obj:`str`): the path to the appointments folder. - Returns: - :obj:`bool`: True if the appointment is accepted by the tower and the receipt is properly stored. False if any - error occurs during the process. + :obj:`tuple`: A tuple (`:obj:Appointment `, :obj:`str`) containing the + appointment and the tower's signature. + + Raises: + :obj:`InvalidParameter `: if `appointment_data` or any of its fields is + invalid. + :obj:`ValueError`: if the appointment cannot be signed. + :obj:`ConnectionError`: if the client cannot connect to the tower. + :obj:`TowerResponseError `: if the tower responded with an error, or the + response was invalid. """ - if appointment_data is None: - logger.error("The provided appointment JSON is empty") - return False - - if not is_256b_hex_str(appointment_data.get("tx_id")): - logger.error("The provided txid is not valid") - return False + if not appointment_data: + raise InvalidParameter("The provided appointment JSON is empty") tx_id = appointment_data.get("tx_id") tx = appointment_data.get("tx") - if None not in [tx_id, tx]: - appointment_data["locator"] = compute_locator(tx_id) - appointment_data["encrypted_blob"] = Cryptographer.encrypt(Blob(tx), tx_id) + if not is_256b_hex_str(tx_id): + raise InvalidParameter("The provided locator is wrong or missing") - else: - logger.error("Appointment data is missing some fields") - return False + if not tx: + raise InvalidParameter("The provided data is missing the transaction") + appointment_data["locator"] = compute_locator(tx_id) + appointment_data["encrypted_blob"] = Cryptographer.encrypt(Blob(tx), tx_id) appointment = Appointment.from_dict(appointment_data) signature = Cryptographer.sign(appointment.serialize(), cli_sk) - if not (appointment and signature): - return False + # FIXME: the cryptographer should return exception we can capture + if not signature: + raise ValueError("The provided appointment cannot be signed") data = {"appointment": appointment.to_dict(), "signature": signature} # Send appointment to the server. - add_appointment_endpoint = "{}/add_appointment".format(teos_url) logger.info("Sending appointment to the Eye of Satoshi") - server_response = post_request(data, add_appointment_endpoint) - if server_response is None: - return False + add_appointment_endpoint = "{}/add_appointment".format(teos_url) + response = process_post_response(post_request(data, add_appointment_endpoint)) - response_json = process_post_response(server_response) - - if response_json is None: - return False - - signature = response_json.get("signature") + signature = response.get("signature") # Check that the server signed the appointment as it should. - if signature is None: - logger.error("The response does not contain the signature of the appointment") - return False + if not signature: + raise TowerResponseError("The response does not contain the signature of the appointment") rpk = Cryptographer.recover_pk(appointment.serialize(), signature) if not Cryptographer.verify_rpk(teos_pk, rpk): - logger.error("The returned appointment's signature is invalid") - return False + raise TowerResponseError("The returned appointment's signature is invalid") logger.info("Appointment accepted and signed by the Eye of Satoshi") - logger.info("Remaining slots: {}".format(response_json.get("available_slots"))) + logger.info("Remaining slots: {}".format(response.get("available_slots"))) - # All good, store appointment and signature - return save_appointment_receipt(appointment.to_dict(), signature, appointments_folder_path) + return appointment, signature def get_appointment(locator, cli_sk, teos_pk, teos_url): @@ -150,17 +145,20 @@ def get_appointment(locator, cli_sk, teos_pk, teos_url): teos_url (:obj:`str`): the teos base url. Returns: - :obj:`dict` or :obj:`None`: a dictionary containing the appointment data if the locator is valid and the tower - responds. ``None`` otherwise. + :obj:`dict`: a dictionary containing the appointment data. + + Raises: + :obj:`InvalidParameter `: if `appointment_data` or any of its fields is + invalid. + :obj:`ConnectionError`: if the client cannot connect to the tower. + :obj:`TowerResponseError `: if the tower responded with an error, or the + response was invalid. """ # FIXME: All responses from the tower should be signed. Not using teos_pk atm. - valid_locator = is_locator(locator) - - if not valid_locator: - logger.error("The provided locator is not valid", locator=locator) - return None + if not is_locator(locator): + raise InvalidParameter("The provided locator is not valid", locator=locator) message = "get appointment {}".format(locator) signature = Cryptographer.sign(message.encode(), cli_sk) @@ -169,10 +167,9 @@ def get_appointment(locator, cli_sk, teos_pk, teos_url): # Send request to the server. get_appointment_endpoint = "{}/get_appointment".format(teos_url) logger.info("Sending appointment to the Eye of Satoshi") - server_response = post_request(data, get_appointment_endpoint) - response_json = process_post_response(server_response) + response = process_post_response(post_request(data, get_appointment_endpoint)) - return response_json + return response def load_keys(teos_pk_path, cli_sk_path, cli_pk_path): @@ -185,45 +182,41 @@ def load_keys(teos_pk_path, cli_sk_path, cli_pk_path): cli_pk_path (:obj:`str`): path to the client public key file. Returns: - :obj:`tuple` or ``None``: a three-item tuple containing a ``PrivateKey``, a ``PublicKey`` and a ``str`` - representing the tower pk, user sk and user compressed pk respectively if all keys can be loaded. - ``None`` otherwise. + :obj:`tuple`: a three-item tuple containing a ``PrivateKey``, a ``PublicKey`` and a ``str`` + representing the tower pk, user sk and user compressed pk respectively. + + Raises: + :obj:`InvalidKey `: if any of the keys is invalid or cannot be loaded. """ - if teos_pk_path is None: - logger.error("TEOS's public key file not found. Please check your settings") - return None + if not teos_pk_path: + raise InvalidKey("TEOS's public key file not found. Please check your settings") - if cli_sk_path is None: - logger.error("Client's private key file not found. Please check your settings") - return None + if not cli_sk_path: + raise InvalidKey("Client's private key file not found. Please check your settings") - if cli_pk_path is None: - logger.error("Client's public key file not found. Please check your settings") - return None + if not cli_pk_path: + raise InvalidKey("Client's public key file not found. Please check your settings") try: teos_pk_der = Cryptographer.load_key_file(teos_pk_path) teos_pk = PublicKey(teos_pk_der) except ValueError: - logger.error("TEOS public key is invalid or cannot be parsed") - return None + raise InvalidKey("TEOS public key is invalid or cannot be parsed") cli_sk_der = Cryptographer.load_key_file(cli_sk_path) cli_sk = Cryptographer.load_private_key_der(cli_sk_der) if cli_sk is None: - logger.error("Client private key is invalid or cannot be parsed") - return None + raise InvalidKey("Client private key is invalid or cannot be parsed") try: cli_pk_der = Cryptographer.load_key_file(cli_pk_path) compressed_cli_pk = Cryptographer.get_compressed_pk(PublicKey(cli_pk_der)) except ValueError: - logger.error("Client public key is invalid or cannot be parsed") - return None + raise InvalidKey("Client public key is invalid or cannot be parsed") return teos_pk, cli_sk, compressed_cli_pk @@ -237,26 +230,25 @@ def post_request(data, endpoint): endpoint (:obj:`str`): the endpoint to send the post request. Returns: - :obj:`dict` or ``None``: a json-encoded dictionary with the server response if the data can be posted. - ``None`` otherwise. + :obj:`dict`: a json-encoded dictionary with the server response if the data can be posted. + + Raises: + :obj:`ConnectionError`: if the client cannot connect to the tower. """ try: return requests.post(url=endpoint, json=data, timeout=5) except ConnectTimeout: - logger.error("Can't connect to the Eye of Satoshi's API. Connection timeout") + message = "Can't connect to the Eye of Satoshi's API. Connection timeout" except ConnectionError: - logger.error("Can't connect to the Eye of Satoshi's API. Server cannot be reached") + message = "Can't connect to the Eye of Satoshi's API. Server cannot be reached" except (InvalidSchema, MissingSchema, InvalidURL): - logger.error("Invalid URL. No schema, or invalid schema, found ({})".format(endpoint)) + message = "Invalid URL. No schema, or invalid schema, found ({})".format(endpoint) - except requests.exceptions.Timeout: - logger.error("The request timed out") - - return None + raise ConnectionError(message) def process_post_response(response): @@ -267,27 +259,26 @@ def process_post_response(response): response (:obj:`requests.models.Response`): a ``Response`` object obtained from the request. Returns: - :obj:`dict` or :obj:`None`: a dictionary containing the tower's response data if the response type is - ``HTTP_OK`` and the response can be properly parsed. ``None`` otherwise. - """ + :obj:`dict`: a dictionary containing the tower's response data if the response type is + ``HTTP_OK``. - if not response: - return None + Raises: + :obj:`TowerResponseError `: if the tower responded with an error, or the + response was invalid. + """ try: response_json = response.json() except (json.JSONDecodeError, AttributeError): - logger.error( + raise TowerResponseError( "The server returned a non-JSON response", status_code=response.status_code, reason=response.reason ) - return None if response.status_code != constants.HTTP_OK: - logger.error( + raise TowerResponseError( "The server returned an error", status_code=response.status_code, reason=response.reason, data=response_json ) - return None return response_json @@ -301,15 +292,18 @@ def parse_add_appointment_args(args): option and the path to a file containing a json encoded appointment. Returns: - :obj:`dict` or :obj:`None`: A dictionary containing the appointment data if it can be loaded. ``None`` - otherwise. + :obj:`dict`: A dictionary containing the appointment data. + + Raises: + :obj:`InvalidParameter `: if the appointment data is not JSON encoded. + :obj:`FileNotFoundError`: if -f is passed and the appointment file is not found. + :obj:`IOError`: if -f was passed and the file cannot be read. """ use_help = "Use 'help add_appointment' for help of how to use the command" if not args: - logger.error("No appointment data provided. " + use_help) - return None + raise InvalidParameter("No appointment data provided. " + use_help) arg_opt = args.pop(0) @@ -320,22 +314,20 @@ def parse_add_appointment_args(args): if arg_opt in ["-f", "--file"]: fin = args.pop(0) if not os.path.isfile(fin): - logger.error("Can't find file", filename=fin) - return None + raise FileNotFoundError("Cannot find {}".format(fin)) try: with open(fin) as f: appointment_data = json.load(f) except IOError as e: - logger.error("I/O error", errno=e.errno, error=e.strerror) - return None + raise IOError("Cannot read appointment file. {}".format(str(e))) + else: appointment_data = json.loads(arg_opt) except json.JSONDecodeError: - logger.error("Non-JSON encoded data provided as appointment. " + use_help) - return None + raise InvalidParameter("Non-JSON encoded data provided as appointment. " + use_help) return appointment_data @@ -349,9 +341,6 @@ def save_appointment_receipt(appointment, signature, appointments_folder_path): signature (:obj:`str`): the signature of the appointment performed by the tower. appointments_folder_path (:obj:`str`): the path to the appointments folder. - Returns: - :obj:`bool`: True if the appointment if properly saved. False otherwise. - Raises: IOError: if an error occurs whilst writing the file on disk. """ @@ -370,14 +359,12 @@ def save_appointment_receipt(appointment, signature, appointments_folder_path): with open(filename, "w") as f: json.dump(data, f) logger.info("Appointment saved at {}".format(filename)) - return True except IOError as e: - logger.error("There was an error while saving the appointment", error=e) - return False + raise IOError("There was an error while saving the appointment. {}".format(e)) -def main(args, command_line_conf): +def main(command, args, command_line_conf): # Loads config and sets up the data folder and log file config_loader = ConfigLoader(DATA_DIR, CONF_FILE_NAME, DEFAULT_CONF, command_line_conf) config = config_loader.build_config() @@ -391,68 +378,59 @@ def main(args, command_line_conf): if not teos_url.startswith("http"): teos_url = "http://" + teos_url - keys = load_keys(config.get("TEOS_PUBLIC_KEY"), config.get("CLI_PRIVATE_KEY"), config.get("CLI_PUBLIC_KEY")) - if keys is not None: - teos_pk, cli_sk, compressed_cli_pk = keys + try: + teos_pk, cli_sk, compressed_cli_pk = load_keys( + config.get("TEOS_PUBLIC_KEY"), config.get("CLI_PRIVATE_KEY"), config.get("CLI_PUBLIC_KEY") + ) - try: + if command == "register": + register_data = register(compressed_cli_pk, teos_url) + logger.info("Registration succeeded. Available slots: {}".format(register_data.get("available_slots"))) + + if command == "add_appointment": + appointment_data = parse_add_appointment_args(args) + appointment, signature = add_appointment(appointment_data, cli_sk, teos_pk, teos_url) + save_appointment_receipt(appointment.to_dict(), signature, config.get("APPOINTMENTS_FOLDER_NAME")) + + elif command == "get_appointment": + if not args: + logger.error("No arguments were given") + + else: + arg_opt = args.pop(0) + + if arg_opt in ["-h", "--help"]: + sys.exit(help_get_appointment()) + + appointment_data = get_appointment(arg_opt, cli_sk, teos_pk, teos_url) + if appointment_data: + print(appointment_data) + + elif command == "help": if args: command = args.pop(0) - if command in commands: - if command == "register": - register_data = register(compressed_cli_pk, teos_url) - if register_data: - print(register_data) + if command == "register": + sys.exit(help_register()) - if command == "add_appointment": - # Get appointment data from user. - appointment_data = parse_add_appointment_args(args) - add_appointment( - appointment_data, cli_sk, teos_pk, teos_url, config.get("APPOINTMENTS_FOLDER_NAME") - ) + if command == "add_appointment": + sys.exit(help_add_appointment()) - elif command == "get_appointment": - if not args: - logger.error("No arguments were given") - - else: - arg_opt = args.pop(0) - - if arg_opt in ["-h", "--help"]: - sys.exit(help_get_appointment()) - - appointment_data = get_appointment(arg_opt, cli_sk, teos_pk, teos_url) - if appointment_data: - print(appointment_data) - - elif command == "help": - if args: - command = args.pop(0) - - if command == "register": - sys.exit(help_register()) - - if command == "add_appointment": - sys.exit(help_add_appointment()) - - elif command == "get_appointment": - sys.exit(help_get_appointment()) - - else: - logger.error("Unknown command. Use help to check the list of available commands") - - else: - sys.exit(show_usage()) + elif command == "get_appointment": + sys.exit(help_get_appointment()) else: logger.error("Unknown command. Use help to check the list of available commands") else: - logger.error("No command provided. Use help to check the list of available commands") + sys.exit(show_usage()) - except json.JSONDecodeError: - logger.error("Non-JSON encoded appointment passed as parameter") + except (FileNotFoundError, IOError, ConnectionError, ValueError) as e: + logger.error(str(e)) + except (InvalidKey, InvalidParameter, TowerResponseError) as e: + logger.error(e.reason, **e.params) + except Exception as e: + logger.error("Unknown error occurred", error=str(e)) if __name__ == "__main__": @@ -477,7 +455,13 @@ if __name__ == "__main__": if opt in ["-h", "--help"]: sys.exit(show_usage()) - main(args, command_line_conf) + command = args.pop(0) + if command in commands: + main(command, args, command_line_conf) + elif not command: + logger.error("No command provided. Use help to check the list of available commands") + else: + logger.error("Unknown command. Use help to check the list of available commands") except GetoptError as e: logger.error("{}".format(e)) diff --git a/test/cli/unit/test_teos_cli.py b/test/cli/unit/test_teos_cli.py index 52a32d6..fc6419b 100644 --- a/test/cli/unit/test_teos_cli.py +++ b/test/cli/unit/test_teos_cli.py @@ -1,19 +1,22 @@ import os import json import shutil +import pytest import responses from binascii import hexlify from coincurve import PrivateKey from requests.exceptions import ConnectionError +from common.blob import Blob import common.cryptographer from common.logger import Logger from common.tools import compute_locator from common.appointment import Appointment from common.cryptographer import Cryptographer -from common.blob import Blob import cli.teos_cli as teos_cli +from cli.exceptions import InvalidParameter, InvalidKey, TowerResponseError + from test.cli.unit.conftest import get_random_value_hex, get_config common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix=teos_cli.LOG_PREFIX) @@ -84,9 +87,7 @@ def test_add_appointment(): "available_slots": 100, } responses.add(responses.POST, add_appointment_endpoint, json=response, status=200) - result = teos_cli.add_appointment( - dummy_appointment_data, dummy_cli_sk, dummy_teos_pk, teos_url, config.get("APPOINTMENTS_FOLDER_NAME") - ) + result = teos_cli.add_appointment(dummy_appointment_data, dummy_cli_sk, dummy_teos_pk, teos_url) assert len(responses.calls) == 1 assert responses.calls[0].request.url == add_appointment_endpoint @@ -105,13 +106,9 @@ def test_add_appointment_with_invalid_signature(monkeypatch): } responses.add(responses.POST, add_appointment_endpoint, json=response, status=200) - result = teos_cli.add_appointment( - dummy_appointment_data, dummy_cli_sk, dummy_teos_pk, teos_url, config.get("APPOINTMENTS_FOLDER_NAME") - ) - assert result is False - - shutil.rmtree(config.get("APPOINTMENTS_FOLDER_NAME")) + with pytest.raises(TowerResponseError): + teos_cli.add_appointment(dummy_appointment_data, dummy_cli_sk, dummy_teos_pk, teos_url) @responses.activate @@ -138,7 +135,8 @@ def test_get_appointment_err(): # Test that get_appointment handles a connection error appropriately. responses.add(responses.POST, get_appointment_endpoint, body=ConnectionError()) - assert not teos_cli.get_appointment(locator, dummy_cli_sk, dummy_teos_pk, teos_url) + with pytest.raises(ConnectionError): + teos_cli.get_appointment(locator, dummy_cli_sk, dummy_teos_pk, teos_url) def test_load_keys(): @@ -150,7 +148,7 @@ def test_load_keys(): f.write(dummy_cli_sk.to_der()) with open(public_key_file_path, "wb") as f: f.write(dummy_cli_compressed_pk) - with open(empty_file_path, "wb") as f: + with open(empty_file_path, "wb"): pass # Now we can test the function passing the using this files (we'll use the same pk for both) @@ -158,25 +156,32 @@ def test_load_keys(): assert isinstance(r, tuple) assert len(r) == 3 - # If any param does not match we should get None as result - assert teos_cli.load_keys(None, private_key_file_path, public_key_file_path) is None - assert teos_cli.load_keys(public_key_file_path, None, public_key_file_path) is None - assert teos_cli.load_keys(public_key_file_path, private_key_file_path, None) is None + # If any param does not match the expected, we should get an InvalidKey exception + with pytest.raises(InvalidKey): + teos_cli.load_keys(None, private_key_file_path, public_key_file_path) + with pytest.raises(InvalidKey): + teos_cli.load_keys(public_key_file_path, None, public_key_file_path) + with pytest.raises(InvalidKey): + teos_cli.load_keys(public_key_file_path, private_key_file_path, None) # The same should happen if we pass a public key where a private should be, for instance - assert teos_cli.load_keys(private_key_file_path, public_key_file_path, private_key_file_path) is None + with pytest.raises(InvalidKey): + teos_cli.load_keys(private_key_file_path, public_key_file_path, private_key_file_path) # Same if any of the files is empty - assert teos_cli.load_keys(empty_file_path, private_key_file_path, public_key_file_path) is None - assert teos_cli.load_keys(public_key_file_path, empty_file_path, public_key_file_path) is None - assert teos_cli.load_keys(public_key_file_path, private_key_file_path, empty_file_path) is None + with pytest.raises(InvalidKey): + teos_cli.load_keys(empty_file_path, private_key_file_path, public_key_file_path) + with pytest.raises(InvalidKey): + teos_cli.load_keys(public_key_file_path, empty_file_path, public_key_file_path) + with pytest.raises(InvalidKey): + teos_cli.load_keys(public_key_file_path, private_key_file_path, empty_file_path) + # Remove the tmp files os.remove(private_key_file_path) os.remove(public_key_file_path) os.remove(empty_file_path) -# WIP: HERE @responses.activate def test_post_request(): response = { @@ -207,24 +212,23 @@ def test_process_post_response(): # If we modify the response code for a rejection (lets say 404) we should get None responses.replace(responses.POST, add_appointment_endpoint, json=response, status=404) - r = teos_cli.post_request(json.dumps(dummy_appointment_data), add_appointment_endpoint) - assert teos_cli.process_post_response(r) is None + with pytest.raises(TowerResponseError): + r = teos_cli.post_request(json.dumps(dummy_appointment_data), add_appointment_endpoint) + teos_cli.process_post_response(r) - # The same should happen if the response is not in json + # The same should happen if the response is not in json independently of the return type responses.replace(responses.POST, add_appointment_endpoint, status=404) - r = teos_cli.post_request(json.dumps(dummy_appointment_data), add_appointment_endpoint) - assert teos_cli.process_post_response(r) is None + with pytest.raises(TowerResponseError): + r = teos_cli.post_request(json.dumps(dummy_appointment_data), add_appointment_endpoint) + teos_cli.process_post_response(r) + + responses.replace(responses.POST, add_appointment_endpoint, status=200) + with pytest.raises(TowerResponseError): + r = teos_cli.post_request(json.dumps(dummy_appointment_data), add_appointment_endpoint) + teos_cli.process_post_response(r) def test_parse_add_appointment_args(): - # If no args are passed, function should fail. - appt_data = teos_cli.parse_add_appointment_args(None) - assert not appt_data - - # If file doesn't exist, function should fail. - appt_data = teos_cli.parse_add_appointment_args(["-f", "nonexistent_file"]) - assert not appt_data - # If file exists and has data in it, function should work. with open("appt_test_file", "w") as f: json.dump(dummy_appointment_data, f) @@ -232,12 +236,22 @@ def test_parse_add_appointment_args(): appt_data = teos_cli.parse_add_appointment_args(["-f", "appt_test_file"]) assert appt_data - os.remove("appt_test_file") - # If appointment json is passed in, function should work. appt_data = teos_cli.parse_add_appointment_args([json.dumps(dummy_appointment_data)]) assert appt_data + os.remove("appt_test_file") + + +def test_parse_add_appointment_args_wrong(): + # If no args are passed, function should fail. + with pytest.raises(InvalidParameter): + teos_cli.parse_add_appointment_args(None) + + # If file doesn't exist, function should fail. + with pytest.raises(FileNotFoundError): + teos_cli.parse_add_appointment_args(["-f", "nonexistent_file"]) + def test_save_appointment_receipt(monkeypatch): appointments_folder = "test_appointments_receipts" diff --git a/test/teos/e2e/test_basic_e2e.py b/test/teos/e2e/test_basic_e2e.py index ab47c49..5c1dc86 100644 --- a/test/teos/e2e/test_basic_e2e.py +++ b/test/teos/e2e/test_basic_e2e.py @@ -1,8 +1,10 @@ +import pytest from time import sleep from riemann.tx import Tx from binascii import hexlify from coincurve import PrivateKey +from cli.exceptions import TowerResponseError from cli import teos_cli, DATA_DIR, DEFAULT_CONF, CONF_FILE_NAME import common.cryptographer @@ -48,9 +50,7 @@ def get_appointment_info(locator, sk=cli_sk): def add_appointment(appointment_data, sk=cli_sk): - return teos_cli.add_appointment( - appointment_data, sk, teos_pk, teos_base_endpoint, cli_config.get("APPOINTMENTS_FOLDER_NAME") - ) + return teos_cli.add_appointment(appointment_data, sk, teos_pk, teos_base_endpoint) def test_commands_non_registered(bitcoin_cli, create_txs): @@ -61,10 +61,12 @@ def test_commands_non_registered(bitcoin_cli, create_txs): commitment_tx_id = bitcoin_cli.decoderawtransaction(commitment_tx).get("txid") appointment_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx) - assert add_appointment(appointment_data) is False + with pytest.raises(TowerResponseError): + assert add_appointment(appointment_data) # Get appointment - assert get_appointment_info(appointment_data.get("locator")) is None + with pytest.raises(TowerResponseError): + assert get_appointment_info(appointment_data.get("locator")) def test_commands_registered(bitcoin_cli, create_txs): @@ -76,37 +78,39 @@ def test_commands_registered(bitcoin_cli, create_txs): commitment_tx_id = bitcoin_cli.decoderawtransaction(commitment_tx).get("txid") appointment_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx) - assert add_appointment(appointment_data) is True + appointment, available_slots = add_appointment(appointment_data) + assert isinstance(appointment, Appointment) and isinstance(available_slots, str) # Get appointment r = get_appointment_info(appointment_data.get("locator")) - assert r.get("locator") == appointment_data.get("locator") - assert r.get("appointment").get("locator") == appointment_data.get("locator") - assert r.get("appointment").get("encrypted_blob") == appointment_data.get("encrypted_blob") - assert r.get("appointment").get("start_time") == appointment_data.get("start_time") - assert r.get("appointment").get("end_time") == appointment_data.get("end_time") + assert r.get("locator") == appointment.locator + assert r.get("appointment") == appointment.to_dict() def test_appointment_life_cycle(bitcoin_cli, create_txs): # First of all we need to register - # FIXME: requires register command in the cli + teos_cli.register(compressed_cli_pk, teos_base_endpoint) + + # After that we can build an appointment and send it to the tower commitment_tx, penalty_tx = create_txs commitment_tx_id = bitcoin_cli.decoderawtransaction(commitment_tx).get("txid") appointment_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx) locator = compute_locator(commitment_tx_id) + appointment, available_slots = add_appointment(appointment_data) - assert add_appointment(appointment_data) is True - + # Get the information from the tower to check that it matches appointment_info = get_appointment_info(locator) - assert appointment_info is not None assert appointment_info.get("status") == "being_watched" + assert appointment_info.get("locator") == locator + assert appointment_info.get("appointment") == appointment.to_dict() + # Trigger a breach and check again new_addr = bitcoin_cli.getnewaddress() broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr) - appointment_info = get_appointment_info(locator) assert appointment_info is not None assert appointment_info.get("status") == "dispute_responded" + assert appointment_info.get("locator") == locator # It can be also checked by ensuring that the penalty transaction made it to the network penalty_tx_id = bitcoin_cli.decoderawtransaction(penalty_tx).get("txid") @@ -120,13 +124,12 @@ def test_appointment_life_cycle(bitcoin_cli, create_txs): assert False # Now let's mine some blocks so the appointment reaches its end. - # Since we are running all the nodes remotely data may take more time than normal, and some confirmations may be - # missed, so we generate more than enough confirmations and add some delays. - for _ in range(int(1.5 * END_TIME_DELTA)): - sleep(1) + for _ in range(END_TIME_DELTA): bitcoin_cli.generatetoaddress(1, new_addr) - assert get_appointment_info(locator) is None + # The appointment is no longer in the tower + with pytest.raises(TowerResponseError): + get_appointment_info(locator) def test_appointment_malformed_penalty(bitcoin_cli, create_txs): @@ -142,18 +145,24 @@ def test_appointment_malformed_penalty(bitcoin_cli, create_txs): appointment_data = build_appointment_data(bitcoin_cli, commitment_tx_id, mod_penalty_tx.hex()) locator = compute_locator(commitment_tx_id) - assert add_appointment(appointment_data) is True + appointment, _ = add_appointment(appointment_data) + + # Get the information from the tower to check that it matches + appointment_info = get_appointment_info(locator) + assert appointment_info.get("status") == "being_watched" + assert appointment_info.get("locator") == locator + assert appointment_info.get("appointment") == appointment.to_dict() # Broadcast the commitment transaction and mine a block new_addr = bitcoin_cli.getnewaddress() broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr) # The appointment should have been removed since the penalty_tx was malformed. - sleep(1) - assert get_appointment_info(locator) is None + with pytest.raises(TowerResponseError): + get_appointment_info(locator) -def test_appointment_wrong_key(bitcoin_cli, create_txs): +def test_appointment_wrong_decryption_key(bitcoin_cli, create_txs): # This tests an appointment encrypted with a key that has not been derived from the same source as the locator. # Therefore the tower won't be able to decrypt the blob once the appointment is triggered. commitment_tx, penalty_tx = create_txs @@ -176,7 +185,6 @@ def test_appointment_wrong_key(bitcoin_cli, create_txs): # Check that the server has accepted the appointment signature = response_json.get("signature") - assert signature is not None rpk = Cryptographer.recover_pk(appointment.serialize(), signature) assert Cryptographer.verify_rpk(teos_pk, rpk) is True assert response_json.get("locator") == appointment.locator @@ -186,14 +194,14 @@ def test_appointment_wrong_key(bitcoin_cli, create_txs): broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr) # The appointment should have been removed since the decryption failed. - sleep(1) - assert get_appointment_info(appointment.locator) is None + with pytest.raises(TowerResponseError): + get_appointment_info(appointment.locator) def test_two_identical_appointments(bitcoin_cli, create_txs): # Tests sending two identical appointments to the tower. # This tests sending an appointment with two valid transaction with the same locator. - # If they come from the same user, the last one will be kept + # If they come from the same user, the last one will be kept. commitment_tx, penalty_tx = create_txs commitment_tx_id = bitcoin_cli.decoderawtransaction(commitment_tx).get("txid") @@ -201,18 +209,16 @@ def test_two_identical_appointments(bitcoin_cli, create_txs): locator = compute_locator(commitment_tx_id) # Send the appointment twice - assert add_appointment(appointment_data) is True - assert add_appointment(appointment_data) is True + add_appointment(appointment_data) + add_appointment(appointment_data) # Broadcast the commitment transaction and mine a block new_addr = bitcoin_cli.getnewaddress() broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr) # The last appointment should have made it to the Responder - sleep(1) appointment_info = get_appointment_info(locator) - assert appointment_info is not None assert appointment_info.get("status") == "dispute_responded" assert appointment_info.get("appointment").get("penalty_rawtx") == penalty_tx @@ -266,7 +272,7 @@ def test_two_identical_appointments(bitcoin_cli, create_txs): def test_two_appointment_same_locator_different_penalty_different_users(bitcoin_cli, create_txs): - # This tests sending an appointment with two valid transaction with the same locator. + # This tests sending an appointment with two valid transaction with the same locator fro different users commitment_tx, penalty_tx1 = create_txs commitment_tx_id = bitcoin_cli.decoderawtransaction(commitment_tx).get("txid") @@ -279,28 +285,24 @@ def test_two_appointment_same_locator_different_penalty_different_users(bitcoin_ appointment2_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx2) locator = compute_locator(commitment_tx_id) - # tmp keys from a different user + # tmp keys for a different user tmp_sk = PrivateKey() tmp_compressed_pk = hexlify(tmp_sk.public_key.format(compressed=True)).decode("utf-8") teos_cli.register(tmp_compressed_pk, teos_base_endpoint) - assert add_appointment(appointment1_data) is True - assert add_appointment(appointment2_data, sk=tmp_sk) is True + appointment, _ = add_appointment(appointment1_data) + appointment_2, _ = add_appointment(appointment2_data, sk=tmp_sk) # Broadcast the commitment transaction and mine a block new_addr = bitcoin_cli.getnewaddress() broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr) # One of the transactions must have made it to the Responder while the other must have been dropped for - # double-spending - sleep(1) - appointment_info = get_appointment_info(locator) - appointment2_info = get_appointment_info(locator, sk=tmp_sk) - - # One of the two request must be None, while the other must be valid - assert (appointment_info is None and appointment2_info is not None) or ( - appointment2_info is None and appointment_info is not None - ) + # double-spending. That means that one of the responses from the tower should fail + appointment_info = None + with pytest.raises(TowerResponseError): + appointment_info = get_appointment_info(locator) + appointment2_info = get_appointment_info(locator, sk=tmp_sk) if appointment_info is None: appointment_info = appointment2_info @@ -308,6 +310,7 @@ def test_two_appointment_same_locator_different_penalty_different_users(bitcoin_ assert appointment_info.get("status") == "dispute_responded" assert appointment_info.get("locator") == appointment1_data.get("locator") + assert appointment_info.get("appointment").get("penalty_tx") == appointment1_data.get("penalty_tx") def test_appointment_shutdown_teos_trigger_back_online(create_txs, bitcoin_cli): @@ -320,7 +323,7 @@ def test_appointment_shutdown_teos_trigger_back_online(create_txs, bitcoin_cli): appointment_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx) locator = compute_locator(commitment_tx_id) - assert add_appointment(appointment_data) is True + appointment, _ = add_appointment(appointment_data) # Restart teos teosd_process.terminate() @@ -329,21 +332,17 @@ def test_appointment_shutdown_teos_trigger_back_online(create_txs, bitcoin_cli): assert teos_pid != teosd_process.pid # Check that the appointment is still in the Watcher - sleep(1) appointment_info = get_appointment_info(locator) - assert appointment_info is not None assert appointment_info.get("status") == "being_watched" + assert appointment_info.get("appointment") == appointment.to_dict() # Trigger appointment after restart new_addr = bitcoin_cli.getnewaddress() broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr) # The appointment should have been moved to the Responder - sleep(1) appointment_info = get_appointment_info(locator) - - assert appointment_info is not None assert appointment_info.get("status") == "dispute_responded" @@ -357,12 +356,12 @@ def test_appointment_shutdown_teos_trigger_while_offline(create_txs, bitcoin_cli appointment_data = build_appointment_data(bitcoin_cli, commitment_tx_id, penalty_tx) locator = compute_locator(commitment_tx_id) - assert add_appointment(appointment_data) is True + appointment, _ = add_appointment(appointment_data) # Check that the appointment is still in the Watcher appointment_info = get_appointment_info(locator) - assert appointment_info is not None assert appointment_info.get("status") == "being_watched" + assert appointment_info.get("appointment") == appointment.to_dict() # Shutdown and trigger teosd_process.terminate() @@ -374,10 +373,7 @@ def test_appointment_shutdown_teos_trigger_while_offline(create_txs, bitcoin_cli assert teos_pid != teosd_process.pid # The appointment should have been moved to the Responder - sleep(1) appointment_info = get_appointment_info(locator) - - assert appointment_info is not None assert appointment_info.get("status") == "dispute_responded" teosd_process.terminate() From b4a453c8de8039d88e51e6fc8f2c028b89151b76 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Sat, 4 Apr 2020 13:09:19 +0200 Subject: [PATCH 2/9] Adds register docs and removes API readme The TEOS-API README is no longer needed after open-sourcing the server --- cli/README.md | 79 ++++++++++++++++++++++++++-------------- cli/TEOS-API.md | 95 ------------------------------------------------- 2 files changed, 53 insertions(+), 121 deletions(-) delete mode 100644 cli/TEOS-API.md diff --git a/cli/README.md b/cli/README.md index 61b9082..ca9051c 100644 --- a/cli/README.md +++ b/cli/README.md @@ -21,15 +21,26 @@ Refer to [INSTALL.md](INSTALL.md) #### Commands -The command line interface has, currently, three commands: +The command line interface has, currently, four commands: -- `add_appointment`: registers a json formatted appointment to the tower. +- `register`: registers your user with the tower. +- `add_appointment`: sends a json formatted appointment to the tower. - `get_appointment`: gets json formatted data about an appointment from the tower. - `help`: shows a list of commands or help for a specific command. +### register +This commands serves as registration. It sends your public key to the tower to create a subscription (fee atm) and returns a number of available appointment slots in the tower. Toping up the subscription can be done by simply sending a register message again. + +Notice that you need to be register before sending any other type of request to the tower. + +#### Usage + + python teos_cli.py register + + ### add_appointment -This command is used to register appointments to the watchtower. Appointments **must** be `json` encoded, and match the following format: +This command is used to send appointments to the watchtower. Appointments **must** be `json` encoded, and match the following format: { "tx": tx, "tx_id": tx_id, @@ -79,33 +90,48 @@ if `-f, --file` **is** specified, then the command expects a path to a json file **not_found** - [{"locator": appointment_locator, - "status":"not_found"}] + { + "locator": l, + "status": "not_found" + } **being_watched** - [{"encrypted_blob": eb, - "end_time": e, - "locator": appointment_locator, - "start_time": s, - "status": "being_watched", - "to_self_delay": d}] + { + "locator": l, + "status": "being_watched", + "appointment": + { + "encrypted_blob": eb, + "end_time": e, + "locator": appointment_locator, + "start_time": s, + "status": "being_watched", + "to_self_delay": d + } + } **dispute_responded** - [{"appointment_end": e, - "dispute_txid": dispute_txid, - "locator": appointment_locator, - "penalty_rawtx": penalty_rawtx, - "penalty_txid": penalty_txid, - "status": "dispute_responded"}] + { + "locator": l, + "status": "dispute_responded", + "appointment": + { + "appointment_end": e, + "dispute_txid": dispute_txid, + "locator": appointment_locator, + "penalty_rawtx": penalty_rawtx, + "penalty_txid": penalty_txid, + "status": "dispute_responded" + } + } #### Usage python teos_cli.py get_appointment - ### help Shows the list of commands or help about how to run a specific command. @@ -118,8 +144,13 @@ or python teos_cli.py help command ## Example +1. Register with the tower. -1. Generate a new dummy appointment. **Note:** this appointment will never be fulfilled (it will eventually expire) since it does not corresopond to a valid transaction. However it can be used to interact with the Eye of Satoshi's API. +``` +python teos_cli.py register +``` + +2. Generate a new dummy appointment. **Note:** this appointment will never be fulfilled (it will eventually expire) since it does not correspond to a valid transaction. However it can be used to interact with the Eye of Satoshi's API. ``` echo '{"tx": "4615a58815475ab8145b6bb90b1268a0dbb02e344ddd483f45052bec1f15b1951c1ee7f070a0993da395a5ee92ea3a1c184b5ffdb2507164bf1f8c1364155d48bdbc882eee0868ca69864a807f213f538990ad16f56d7dfb28a18e69e3f31ae9adad229e3244073b7d643b4597ec88bf247b9f73f301b0f25ae8207b02b7709c271da98af19f1db276ac48ba64f099644af1ae2c90edb7def5e8589a1bb17cc72ac42ecf07dd29cff91823938fd0d772c2c92b7ab050f8837efd46197c9b2b3f", "tx_id": "0b9510d92a50c1d67c6f7fc5d47908d96b3eccdea093d89bcbaf05bcfebdd951", "start_time": 0, "end_time": 0, "to_self_delay": 20}' > dummy_appointment_data.json @@ -127,24 +158,20 @@ or That will create a json file that follows the appointment data structure filled with dummy data and store it in `dummy_appointment_data.json`. **Note**: You'll need to update the `start_time` and `end_time` to match valid block heights. -2. Send the appointment to the tower API. Which will then start monitoring for matching transactions. +3. Send the appointment to the tower API. Which will then start monitoring for matching transactions. ``` python teos_cli.py add_appointment -f dummy_appointment_data.json ``` - This returns a appointment locator that can be used to get updates about this appointment from the tower. + This returns an appointment locator that can be used to get updates about this appointment from the tower. -3. Test that the tower is still watching the appointment by replacing the appointment locator received into the following command: +4. Test that the tower is still watching the appointment by replacing the appointment locator received into the following command: ``` python teos_cli.py get_appointment ``` -## the Eye of Satoshi's API - -If you wish to read about the underlying API, and how to write your own tool to interact with it, refer to [tEOS-API.md](TEOS-API.md). - ## Try our live instance By default, `teos_cli` will connect to your local instance (running on localhost). There are also a couple of live instances running, one for mainet and one for testnet: diff --git a/cli/TEOS-API.md b/cli/TEOS-API.md deleted file mode 100644 index d0323b5..0000000 --- a/cli/TEOS-API.md +++ /dev/null @@ -1,95 +0,0 @@ -## TEOS-API - -### Disclaimer: Everything in here is experimental and subject to change. - -The Eye of Satoshi's REST API consists, currently, of two endpoints: `/` and `/get_appointment` - -`/` is the default endpoint, and is where the appointments should be sent to. `/` accepts `HTTP POST` requests only, with json request body, where data must match the following format: - - {"locator": l, "start_time": s, "end_time": e, - "to_self_delay": d, "encrypted_blob": eb} - -We'll discuss the parameters one by one in the following: - -The locator, `l`, is the first half of the **dispute transaction id** (i.e. the 16 MSB of the dispute_txid encoded in hex). `type(l) = hex encoded str` - -The start\_time, `s`, is the time when the tower will start watching your transaction, and will normally match with whenever you will be offline. `s` is measured in block height, and must be **higher than the current block height**. `type(s) = int` - -The end\_time, `e`, is the time where the tower will stop watching your transaction, and will normally match with whenever you should be back online. `e` is also measured in block height, and must be **higher than** `s`. `type(e) = int` - -The to\_self\_delay, `d`, is the time the tower would have to respond with the **penalty transaction** once the **dispute transaction** is seen in the blockchain. `d` must match with the `OP_CSV` specified in the dispute transaction. If the dispute_delta does not match the `OP_CSV `, the tower would try to respond with the penalty transaction anyway, but success is not guaranteed. `d` is measured in blocks and should be, at least, `20`. `type(d) = int` - -The encrypted\_blob, `eb`, is a data blob containing the `raw penalty transaction` and it is encrypted using `CHACHA20-POLY1305`. The `encryption key` used by the cipher is the sha256 of the **dispute transaction id**, and the `nonce` is a 12-byte long zero byte array: - - sk = sha256(unhexlify(secret)).digest() - nonce = bytearray(12) # b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - -Finally, the encrypted blob must be hex encoded. `type(eb) = hex encoded str` - -The API will return a `application/json` HTTP response code `200/OK` if the appointment is accepted, with the locator encoded in the response text, or a `400/Bad Request` if the appointment is rejected, with the rejection reason encoded in the response text. - -### Alpha release restrictions -The alpha release does not have authentication, payments nor rate limiting, therefore some self imposed restrictions apply: - -- `start_time` should be within the next 6 blocks `[current_time+1, current_time+6]`. -- `end_time` cannot be bigger than (roughtly) a month. That is `4320` blocks on top of `start_time`. - -#### Appointment example - - {"locator": "3c3375883f01027e5ca14f9760a8b853824ca4ebc0258c00e7fae4bae2571a80", - "start_time": 1568118, - "end_time": 1568120, - "to_self_delay": 20, - "encrypted_blob": "6c7687a97e874363e1c2b9a08386125e09ea000a9b4330feb33a5c698265f3565c267554e6fdd7b0544ced026aaab73c255bcc97c18eb9fa704d9cc5f1c83adaf921de7ba62b2b6ddb1bda7775288019ec3708642e738eddc22882abf5b3f4e34ef2d4077ed23e135f7fe22caaec845982918e7df4a3f949cadd2d3e7c541b1dbf77daf64e7ed61531aaa487b468581b5aa7b1da81e2617e351c9d5cf445e3391c3fea4497aaa7ad286552759791b9caa5e4c055d1b38adfceddb1ef2b99e3b467dd0b0b13ce863c1bf6b6f24543c30d"} - -# Get appointment - -`/get_appointment` is an endpoint provided to check the status of the appointments sent to the tower. The endpoint is accessible without any type of authentication for now. `/get_appointment` accepts `HTTP GET` requests only, where the data to be provided must be the **locator** of an appointment. The query must match the following format: - -`https://teos_server:teos_port/get_appointment?locator=appointment_locator` - -**Appointment can be in three states**: - -- `not_found`: meaning the locator is not recognised by the API. This could either mean the locator is wrong, or the appointment has already been fulfilled. -- `being_watched`: the appointment has been accepted by the tower server and it's being watched at the moment. This stage means that the dispute transaction has not been seen yet, and therefore no penalty transaction has been published. -- `dispute_responded`: the dispute was found by the watcher and the corresponding penalty transaction has been broadcast by the node. In this stage the tower is actively monitoring until the penalty transaction reaches enough confirmations and making sure no fork occurs in the meantime. - -### Get appointment response formats - -`/get_appointment` will always reply with `json` containing the information about the requested appointment. The structure is as follows: - -**not_found** - - [{"locator": appointment_locator, - "status":"not_found"}] - -**being_watched** - - [{"encrypted_blob": eb, - "end_time": e, - "locator": appointment_locator, - "start_time": s, - "status": "being_watched", - "to_self_delay": d}] - -**dispute_responded** - - [{"appointment_end": e, - "dispute_txid": dispute_txid, - "locator": appointment_locator, - "penalty_rawtx": penalty_rawtx, - "penalty_txid": penalty_txid, - "status": "dispute_responded"}] - -Notice that the response json always contains a list. Why? It is possible for both parties to send the “same locator” to our service: - -Alice wants to hire us to watch Bob’s commitment transaction. -Bob wants to front-run Alice by creating a job for his “commitment transaction” with a bad encrypted blob. - -In the above scenario, Bob can hire our service with a bad encrypted blob for the locator that should be used by Alice. Our service will try to decrypt both encrypted blobs, find the valid transaction and send it out. More generally, this potential DoS attack is possible of locators are publicly known (i.e. other watching services). - -### Data persistence - -The Eye of Satoshi keeps track of the appointment while they are being monitored, but data is wiped once an appointment has been completed with enough confirmations. Notice that during the alpha there will be no authentication, so data may be wiped periodically. - - From 3ab4e57f4dff15a2b2d28b215468bde9511f84c2 Mon Sep 17 00:00:00 2001 From: Aljaz Ceru Date: Sun, 5 Apr 2020 15:17:16 +0200 Subject: [PATCH 3/9] Dockerfile and entrypoint --- Dockerfile | 11 +++++++++++ entrypoint.sh | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 Dockerfile create mode 100644 entrypoint.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..56be854 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3 +ENV APP_PATH=/srv/python-teos +VOLUME ["~/.teos"] +WORKDIR /srv +RUN mkdir ~/.teos && git clone https://github.com/talaia-labs/python-teos && cd python-teos && pip install -r requirements.txt && python generate_keys.py -d ~/.teos +ENV PYTHONPATH=$APP_PATH +WORKDIR /srv/python-teos +COPY entrypoint.sh /srv/python-teos/entrypoint.sh +RUN chmod +x /srv/python-teos/entrypoint.sh +ENTRYPOINT [ "/srv/python-teos/entrypoint.sh" ] +#CMD ["sh","-c","/usr/local/bin/python3 -m teos.teosd --btcrpcuser=$BTC_RPC_USER --btcrpcpassword=$BTC_RPC_PASSWD --btcrpcconnect=$BTC_RPC_HOST"] diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..354af05 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +START_COMMAND="/usr/local/bin/python3 -m teos.teosd " + +if [[ ! -z ${BTC_RPC_USER} ]]; then + START_COMMAND=$START_COMMAND" --btcrpcuser=""$BTC_RPC_USER" +fi + +if [[ ! -z ${BTC_RPC_HOST} ]]; then + START_COMMAND=$START_COMMAND" --btcrpcconnect=""$BTC_RPC_HOST" +fi + +if [[ ! -z ${BTC_RPC_PASSWD} ]]; then + START_COMMAND=$START_COMMAND" --btcrpcpassword=""$BTC_RPC_PASSWD" +fi + +if [[ ! -z ${BTC_NETWORK} ]]; then + START_COMMAND=$START_COMMAND" --btcnetwork=""$BTC_NETWORK" +fi + +$START_COMMAND \ No newline at end of file From 102b89cb9d6768dfa672ed279967d9a6399dd1cc Mon Sep 17 00:00:00 2001 From: Aljaz Ceru Date: Sun, 5 Apr 2020 15:26:54 +0200 Subject: [PATCH 4/9] updating readme and target change --- Dockerfile | 6 +++--- INSTALL.md | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 56be854..db1226a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,10 @@ FROM python:3 ENV APP_PATH=/srv/python-teos VOLUME ["~/.teos"] WORKDIR /srv -RUN mkdir ~/.teos && git clone https://github.com/talaia-labs/python-teos && cd python-teos && pip install -r requirements.txt && python generate_keys.py -d ~/.teos +RUN mkdir ~/.teos && git clone https://github.com/aljazceru/python-teos.git && cd python-teos && pip install -r requirements.txt && python generate_keys.py -d ~/.teos ENV PYTHONPATH=$APP_PATH WORKDIR /srv/python-teos -COPY entrypoint.sh /srv/python-teos/entrypoint.sh +#COPY entrypoint.sh /srv/python-teos/entrypoint.sh RUN chmod +x /srv/python-teos/entrypoint.sh ENTRYPOINT [ "/srv/python-teos/entrypoint.sh" ] -#CMD ["sh","-c","/usr/local/bin/python3 -m teos.teosd --btcrpcuser=$BTC_RPC_USER --btcrpcpassword=$BTC_RPC_PASSWD --btcrpcconnect=$BTC_RPC_HOST"] + diff --git a/INSTALL.md b/INSTALL.md index c2b9adc..bb7946c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -4,6 +4,14 @@ There are two ways of running `teos`: running it as a module or adding the library to the `PYTHONPATH` env variable. +## Running `teos` as a docker container +`teos` container can be build from Dockerfile attached to the repo. ENV variables are optional, if not set defaults are used. + + git clone https://github.com/talaia-labs/python-teos + cd python-teos + docker build . -t teos + docker run -it -e BTC_RPC_USER= -e BTC_RPC_PASSWD= -e BTC_RPC_HOST= -e BTC_RPC_PORT= teos + ## Running `teos` as a Module The **easiest** way to run `teos` is as a module. To do so you need to use `python -m`. From the teos parent directory run: From cafe282db5e59d90a8df4f6a159e5e21e9bb033d Mon Sep 17 00:00:00 2001 From: Aljaz Ceru Date: Sun, 5 Apr 2020 17:18:01 +0200 Subject: [PATCH 5/9] 0.0.0.0 bind for teos --- teos/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teos/__init__.py b/teos/__init__.py index 707c7ec..ff1db3b 100644 --- a/teos/__init__.py +++ b/teos/__init__.py @@ -1,7 +1,7 @@ import os from teos.utils.auth_proxy import AuthServiceProxy -HOST = "localhost" +HOST = "0.0.0.0" PORT = 9814 DATA_DIR = os.path.expanduser("~/.teos/") CONF_FILE_NAME = "teos.conf" From 4bd1e9c65e51e6966809f98f91b77d3b59b015ce Mon Sep 17 00:00:00 2001 From: Aljaz Ceru Date: Mon, 6 Apr 2020 08:46:25 +0200 Subject: [PATCH 6/9] updates --- Dockerfile | 3 --- INSTALL.md | 8 -------- README.md | 8 ++++++++ entrypoint.sh | 0 4 files changed, 8 insertions(+), 11 deletions(-) mode change 100644 => 100755 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index db1226a..e7b0c2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,4 @@ WORKDIR /srv RUN mkdir ~/.teos && git clone https://github.com/aljazceru/python-teos.git && cd python-teos && pip install -r requirements.txt && python generate_keys.py -d ~/.teos ENV PYTHONPATH=$APP_PATH WORKDIR /srv/python-teos -#COPY entrypoint.sh /srv/python-teos/entrypoint.sh -RUN chmod +x /srv/python-teos/entrypoint.sh ENTRYPOINT [ "/srv/python-teos/entrypoint.sh" ] - diff --git a/INSTALL.md b/INSTALL.md index bb7946c..c2b9adc 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -4,14 +4,6 @@ There are two ways of running `teos`: running it as a module or adding the library to the `PYTHONPATH` env variable. -## Running `teos` as a docker container -`teos` container can be build from Dockerfile attached to the repo. ENV variables are optional, if not set defaults are used. - - git clone https://github.com/talaia-labs/python-teos - cd python-teos - docker build . -t teos - docker run -it -e BTC_RPC_USER= -e BTC_RPC_PASSWD= -e BTC_RPC_HOST= -e BTC_RPC_PORT= teos - ## Running `teos` as a Module The **easiest** way to run `teos` is as a module. To do so you need to use `python -m`. From the teos parent directory run: diff --git a/README.md b/README.md index 0947250..58b9d9d 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,14 @@ The configuration includes, amongst others, where your data folder is placed, wh To run `teos` you need a set of keys (to sign appointments) stored in your data directory. You can follow [generate keys](#generate-keys) to generate them. +## Running `teos` as a docker container +`teos` container can be build from Dockerfile attached to the repo. ENV variables are optional, if not set defaults are used. + + git clone https://github.com/talaia-labs/python-teos + cd python-teos + docker build . -t teos + docker run -it -e BTC_RPC_USER= -e BTC_RPC_PASSWD= -e BTC_RPC_HOST= -e BTC_RPC_PORT= teos + ### Configuration file and command line parameters To change the configuration defaults you can: diff --git a/entrypoint.sh b/entrypoint.sh old mode 100644 new mode 100755 From 62edf51dbd92f834110b9821ab8ed8b274b14be3 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Tue, 7 Apr 2020 12:10:49 +0200 Subject: [PATCH 7/9] Adds API HOST and PORT as configurable parameters. Renames them to API_CONNECT and API_PORT for consistency. --- cli/README.md | 8 ++++---- cli/__init__.py | 4 ++-- cli/help.py | 4 ++-- cli/template.conf | 4 ++-- cli/teos_cli.py | 12 ++++++------ teos/__init__.py | 4 ++-- teos/api.py | 8 +++++--- teos/template.conf | 2 ++ teos/teosd.py | 19 +++++++++++++++++-- test/cli/unit/test_teos_cli.py | 2 +- test/teos/e2e/test_basic_e2e.py | 2 +- test/teos/unit/test_api.py | 10 ++++------ 12 files changed, 48 insertions(+), 31 deletions(-) diff --git a/cli/README.md b/cli/README.md index ca9051c..9ba2e38 100644 --- a/cli/README.md +++ b/cli/README.md @@ -15,8 +15,8 @@ Refer to [INSTALL.md](INSTALL.md) #### Global options -- `-s, --server`: API server where to send the requests. Defaults to 'localhost' (modifiable in conf file). -- `-p, --port` : API port where to send the requests. Defaults to '9814' (modifiable in conf file). +- `--apiconnect`: API server where to send the requests. Defaults to 'localhost' (modifiable in conf file). +- `-apiport` : API port where to send the requests. Defaults to '9814' (modifiable in conf file). - `-h --help`: shows a list of commands or help for a specific command. #### Commands @@ -180,10 +180,10 @@ By default, `teos_cli` will connect to your local instance (running on localhost - mainnet endpoint = `teosmainnet.pisa.watch` ### Connecting to the mainnet instance -Add `-s https://teosmainnet.pisa.watch` to your calls, for example: +Add `--apiconnect https://teosmainnet.pisa.watch` to your calls, for example: ``` -python teos_cli.py -s https://teosmainnet.pisa.watch add_appointment -f dummy_appointment_data.json +python teos_cli.py --apiconnect https://teosmainnet.pisa.watch add_appointment -f dummy_appointment_data.json ``` You can also change the config file to avoid specifying the server every time: diff --git a/cli/__init__.py b/cli/__init__.py index dfc1301..9316550 100644 --- a/cli/__init__.py +++ b/cli/__init__.py @@ -6,8 +6,8 @@ LOG_PREFIX = "cli" # Load config fields DEFAULT_CONF = { - "TEOS_SERVER": {"value": "localhost", "type": str}, - "TEOS_PORT": {"value": 9814, "type": int}, + "API_CONNECT": {"value": "localhost", "type": str}, + "API_PORT": {"value": 9814, "type": int}, "LOG_FILE": {"value": "teos_cli.log", "type": str, "path": True}, "APPOINTMENTS_FOLDER_NAME": {"value": "appointment_receipts", "type": str, "path": True}, "CLI_PUBLIC_KEY": {"value": "cli_pk.der", "type": str, "path": True}, diff --git a/cli/help.py b/cli/help.py index 4ecf172..3635db7 100644 --- a/cli/help.py +++ b/cli/help.py @@ -8,8 +8,8 @@ def show_usage(): "\n\tget_appointment \tGets json formatted data about an appointment from the tower." "\n\thelp \t\t\tShows a list of commands or help for a specific command." "\n\nGLOBAL OPTIONS:" - "\n\t-s, --server \tAPI server where to send the requests. Defaults to 'localhost' (modifiable in conf file)." - "\n\t-p, --port \tAPI port where to send the requests. Defaults to '9814' (modifiable in conf file)." + "\n\t--apiconnect \tAPI server where to send the requests. Defaults to 'localhost' (modifiable in conf file)." + "\n\t--apiport \tAPI port where to send the requests. Defaults to '9814' (modifiable in conf file)." "\n\t-d, --debug \tshows debug information and stores it in teos_cli.log." "\n\t-h --help \tshows this message." ) diff --git a/cli/template.conf b/cli/template.conf index b53fb45..0a1e7a8 100644 --- a/cli/template.conf +++ b/cli/template.conf @@ -1,4 +1,4 @@ [teos] -TEOS_SERVER = localhost -TEOS_PORT = 9814 +api_connect = localhost +api_port = 9814 diff --git a/cli/teos_cli.py b/cli/teos_cli.py index c508379..7799e4e 100644 --- a/cli/teos_cli.py +++ b/cli/teos_cli.py @@ -373,7 +373,7 @@ def main(command, args, command_line_conf): setup_logging(config.get("LOG_FILE"), LOG_PREFIX) # Set the teos url - teos_url = "{}:{}".format(config.get("TEOS_SERVER"), config.get("TEOS_PORT")) + teos_url = "{}:{}".format(config.get("API_CONNECT"), config.get("API_PORT")) # If an http or https prefix if found, leaves the server as is. Otherwise defaults to http. if not teos_url.startswith("http"): teos_url = "http://" + teos_url @@ -438,17 +438,17 @@ if __name__ == "__main__": commands = ["register", "add_appointment", "get_appointment", "help"] try: - opts, args = getopt(argv[1:], "s:p:h", ["server", "port", "help"]) + opts, args = getopt(argv[1:], "h", ["apiconnect=", "apiport=", "help"]) for opt, arg in opts: - if opt in ["-s", "--server"]: + if opt in ["--apiconnect"]: if arg: - command_line_conf["TEOS_SERVER"] = arg + command_line_conf["API_CONNECT"] = arg - if opt in ["-p", "--port"]: + if opt in ["--apiport"]: if arg: try: - command_line_conf["TEOS_PORT"] = int(arg) + command_line_conf["API_PORT"] = int(arg) except ValueError: sys.exit("port must be an integer") diff --git a/teos/__init__.py b/teos/__init__.py index 811e16d..52ae177 100644 --- a/teos/__init__.py +++ b/teos/__init__.py @@ -1,13 +1,13 @@ import os -HOST = "localhost" -PORT = 9814 DATA_DIR = os.path.expanduser("~/.teos/") CONF_FILE_NAME = "teos.conf" LOG_PREFIX = "teos" # Default conf fields DEFAULT_CONF = { + "API_CONNECT": {"value": "localhost", "type": str}, + "API_PORT": {"value": 9814, "type": int}, "BTC_RPC_USER": {"value": "user", "type": str}, "BTC_RPC_PASSWORD": {"value": "passwd", "type": str}, "BTC_RPC_CONNECT": {"value": "127.0.0.1", "type": str}, diff --git a/teos/api.py b/teos/api.py index 0859c86..02bfb79 100644 --- a/teos/api.py +++ b/teos/api.py @@ -3,8 +3,8 @@ import logging from math import ceil from flask import Flask, request, abort, jsonify +from teos import LOG_PREFIX import teos.errors as errors -from teos import HOST, PORT, LOG_PREFIX from teos.inspector import InspectionFailed from teos.gatekeeper import NotEnoughSlots, IdentificationFailure @@ -79,7 +79,9 @@ class API: access. """ - def __init__(self, inspector, watcher, gatekeeper): + def __init__(self, host, port, inspector, watcher, gatekeeper): + self.host = host + self.port = port self.inspector = inspector self.watcher = watcher self.gatekeeper = gatekeeper @@ -341,4 +343,4 @@ class API: logging.getLogger("werkzeug").setLevel(logging.ERROR) os.environ["WERKZEUG_RUN_MAIN"] = "true" - app.run(host=HOST, port=PORT) + app.run(host=self.host, port=self.port) diff --git a/teos/template.conf b/teos/template.conf index 1989de6..035ce8b 100644 --- a/teos/template.conf +++ b/teos/template.conf @@ -11,6 +11,8 @@ feed_connect = 127.0.0.1 feed_port = 28332 [teos] +api_connect = localhost +api_port = 9814 subscription_slots = 100 max_appointments = 1000000 expiry_delta = 6 diff --git a/teos/teosd.py b/teos/teosd.py index 3287581..c7d04ba 100644 --- a/teos/teosd.py +++ b/teos/teosd.py @@ -154,7 +154,8 @@ def main(command_line_conf): # FIXME: 92-block-data-during-bootstrap-db chain_monitor.monitor_chain() gatekeeper = Gatekeeper(UsersDBM(config.get("USERS_DB_PATH")), config.get("DEFAULT_SLOTS")) - API(Inspector(block_processor, config.get("MIN_TO_SELF_DELAY")), watcher, gatekeeper).start() + inspector = Inspector(block_processor, config.get("MIN_TO_SELF_DELAY")) + API(config.get("API_CONNECT"), config.get("API_PORT"), inspector, watcher, gatekeeper).start() except Exception as e: logger.error("An error occurred: {}. Shutting down".format(e)) exit(1) @@ -167,9 +168,23 @@ if __name__ == "__main__": opts, _ = getopt( argv[1:], "h", - ["btcnetwork=", "btcrpcuser=", "btcrpcpassword=", "btcrpcconnect=", "btcrpcport=", "datadir=", "help"], + [ + "apiconnect=", + "apiport=", + "btcnetwork=", + "btcrpcuser=", + "btcrpcpassword=", + "btcrpcconnect=", + "btcrpcport=", + "datadir=", + "help", + ], ) for opt, arg in opts: + if opt in ["--apiconnect"]: + command_line_conf["API_CONNECT"] = arg + if opt in ["--apiport"]: + command_line_conf["API_PORT"] = arg if opt in ["--btcnetwork"]: command_line_conf["BTC_NETWORK"] = arg if opt in ["--btcrpcuser"]: diff --git a/test/cli/unit/test_teos_cli.py b/test/cli/unit/test_teos_cli.py index fc6419b..1f11ed1 100644 --- a/test/cli/unit/test_teos_cli.py +++ b/test/cli/unit/test_teos_cli.py @@ -30,7 +30,7 @@ dummy_teos_sk = PrivateKey.from_int(2) dummy_teos_pk = dummy_teos_sk.public_key another_sk = PrivateKey.from_int(3) -teos_url = "http://{}:{}".format(config.get("TEOS_SERVER"), config.get("TEOS_PORT")) +teos_url = "http://{}:{}".format(config.get("API_CONNECT"), config.get("API_PORT")) add_appointment_endpoint = "{}/add_appointment".format(teos_url) register_endpoint = "{}/register".format(teos_url) get_appointment_endpoint = "{}/get_appointment".format(teos_url) diff --git a/test/teos/e2e/test_basic_e2e.py b/test/teos/e2e/test_basic_e2e.py index 5c1dc86..b4db1aa 100644 --- a/test/teos/e2e/test_basic_e2e.py +++ b/test/teos/e2e/test_basic_e2e.py @@ -26,7 +26,7 @@ from test.teos.e2e.conftest import ( cli_config = get_config(DATA_DIR, CONF_FILE_NAME, DEFAULT_CONF) common.cryptographer.logger = Logger(actor="Cryptographer", log_name_prefix="") -teos_base_endpoint = "http://{}:{}".format(cli_config.get("TEOS_SERVER"), cli_config.get("TEOS_PORT")) +teos_base_endpoint = "http://{}:{}".format(cli_config.get("API_CONNECT"), cli_config.get("API_PORT")) teos_add_appointment_endpoint = "{}/add_appointment".format(teos_base_endpoint) teos_get_appointment_endpoint = "{}/get_appointment".format(teos_base_endpoint) diff --git a/test/teos/unit/test_api.py b/test/teos/unit/test_api.py index 210240f..db59a3b 100644 --- a/test/teos/unit/test_api.py +++ b/test/teos/unit/test_api.py @@ -3,7 +3,6 @@ from shutil import rmtree from binascii import hexlify from teos.api import API -from teos import HOST, PORT import teos.errors as errors from teos.watcher import Watcher from teos.inspector import Inspector @@ -22,8 +21,9 @@ from common.constants import ( ENCRYPTED_BLOB_MAX_SIZE_HEX, ) +config = get_config() -TEOS_API = "http://{}:{}".format(HOST, PORT) +TEOS_API = "http://{}:{}".format(config.get("API_HOST"), config.get("API_PORT")) register_endpoint = "{}/register".format(TEOS_API) add_appointment_endpoint = "{}/add_appointment".format(TEOS_API) get_appointment_endpoint = "{}/get_appointment".format(TEOS_API) @@ -38,8 +38,6 @@ TWO_SLOTS_BLOTS = "A" * ENCRYPTED_BLOB_MAX_SIZE_HEX + "AA" appointments = {} locator_dispute_tx_map = {} -config = get_config() - client_sk, client_pk = generate_keypair() compressed_client_pk = hexlify(client_pk.format(compressed=True)).decode("utf-8") @@ -62,8 +60,8 @@ def api(db_manager, carrier, block_processor, gatekeeper, run_bitcoind): responder = Responder(db_manager, carrier, block_processor) watcher = Watcher(db_manager, block_processor, responder, sk.to_der(), MAX_APPOINTMENTS, config.get("EXPIRY_DELTA")) - - api = API(Inspector(block_processor, config.get("MIN_TO_SELF_DELAY")), watcher, gatekeeper) + inspector = Inspector(block_processor, config.get("MIN_TO_SELF_DELAY")) + api = API(config.get("API_HOST"), config.get("API_PORT"), inspector, watcher, gatekeeper) return api From e77f7ea2000a90ae0ee518d76cbcce12508e9770 Mon Sep 17 00:00:00 2001 From: Aljaz Ceru Date: Thu, 9 Apr 2020 08:46:28 +0200 Subject: [PATCH 8/9] update --- Dockerfile | 1 + README.md | 4 ++-- entrypoint.sh | 11 ++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index e7b0c2d..09431e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,4 +5,5 @@ WORKDIR /srv RUN mkdir ~/.teos && git clone https://github.com/aljazceru/python-teos.git && cd python-teos && pip install -r requirements.txt && python generate_keys.py -d ~/.teos ENV PYTHONPATH=$APP_PATH WORKDIR /srv/python-teos +EXPOSE 9814/tcp ENTRYPOINT [ "/srv/python-teos/entrypoint.sh" ] diff --git a/README.md b/README.md index 44bd374..0b4c60d 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ To run `teos` you need a set of keys (to sign appointments) stored in your data git clone https://github.com/talaia-labs/python-teos cd python-teos docker build . -t teos - docker run -it -e BTC_RPC_USER= -e BTC_RPC_PASSWD= -e BTC_RPC_HOST= -e BTC_RPC_PORT= teos + docker run -it -e BTC_RPC_USER= -e BTC_RPC_PASSWD= -e BTC_RPC_HOST= -e BTC_RPC_PORT= -p 9814:9814/tcp teos ### Configuration file and command line parameters @@ -129,4 +129,4 @@ cp ~/.teos/teos_pk.der ~/.teos_cli/teos_pk.der ``` ## Contributing -Refer to [CONTRIBUTING.md](CONTRIBUTING.md) \ No newline at end of file +Refer to [CONTRIBUTING.md](CONTRIBUTING.md) diff --git a/entrypoint.sh b/entrypoint.sh index 354af05..a82b8b1 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -18,4 +18,13 @@ if [[ ! -z ${BTC_NETWORK} ]]; then START_COMMAND=$START_COMMAND" --btcnetwork=""$BTC_NETWORK" fi -$START_COMMAND \ No newline at end of file +if [[ ! -z ${API_CONNECT} ]]; then + START_COMMAND=$START_COMMAND" --apiconnect=""$API_CONNECT" +fi + +if [[ ! -z ${API_PORT} ]]; then + START_COMMAND=$START_COMMAND" --apiport=""$API_PORT" +fi + + +$START_COMMAND From 38bcce9362d57c51bea08c4c79aa72ec738a3591 Mon Sep 17 00:00:00 2001 From: Aljaz Ceru Date: Thu, 9 Apr 2020 08:48:02 +0200 Subject: [PATCH 9/9] pr conflict resolving --- teos/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/teos/__init__.py b/teos/__init__.py index 4a56111..bdbb1a3 100644 --- a/teos/__init__.py +++ b/teos/__init__.py @@ -1,13 +1,13 @@ import os -HOST = "0.0.0.0" -PORT = 9814 DATA_DIR = os.path.expanduser("~/.teos/") CONF_FILE_NAME = "teos.conf" LOG_PREFIX = "teos" # Default conf fields DEFAULT_CONF = { + "API_CONNECT": {"value": "localhost", "type": str}, + "API_PORT": {"value": 9814, "type": int}, "BTC_RPC_USER": {"value": "user", "type": str}, "BTC_RPC_PASSWORD": {"value": "passwd", "type": str}, "BTC_RPC_CONNECT": {"value": "127.0.0.1", "type": str}, @@ -25,3 +25,4 @@ DEFAULT_CONF = { "APPOINTMENTS_DB_PATH": {"value": "appointments", "type": str, "path": True}, "USERS_DB_PATH": {"value": "users", "type": str, "path": True}, } +