Skip to content
This repository was archived by the owner on Sep 26, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
plugin - Adds ping command to plugin
  • Loading branch information
sr-gi committed May 19, 2021
commit 78e5eac67e56f8c5647eca77b89c01c5863f36df
1 change: 1 addition & 0 deletions watchtower-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ commitment transaction is generated. It also keeps a summary of the messages sen

The plugin has the following methods:

- `pingtower ip[:port]`: pings a tower to check if it's online.
- `registertower tower_id` : registers the user id (compressed public key) with a given tower.
- `list_towers`: lists all registered towers.
- `gettowerinfo tower_id`: gets all the locally stored data about a given tower.
Expand Down
36 changes: 35 additions & 1 deletion watchtower-plugin/arg_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,48 @@
from common.exceptions import InvalidParameter


def parse_ping_arguments(host, port, config):
"""
Parses the arguments of the ping command and checks that they are correct.

Args:
host (:obj:`str`): the ip or hostname to connect to.
port (:obj:`int`): the port to connect to, optional.
config: (:obj:`dict`): the configuration dictionary.

Returns:
:obj:`tuple`: the network address.

Raises:
:obj:`common.exceptions.InvalidParameter`: if any of the parameters is wrong or missing.
"""

if not isinstance(host, str):
raise InvalidParameter(f"host must be string not {str(host)}")

# host and port specified
if host and port:
tower_netaddr = f"{host}:{port}"
else:
# host was specified, but no port, defaulting
if ":" not in host:
tower_netaddr = f"{host}:{config.get('DEFAULT_PORT')}"
elif host.endswith(":"):
tower_netaddr = f"{host}{config.get('DEFAULT_PORT')}"
else:
tower_netaddr = host

return tower_netaddr


def parse_register_arguments(tower_id, host, port, config):
"""
Parses the arguments of the register command and checks that they are correct.

Args:
tower_id (:obj:`str`): the identifier of the tower to connect to (a compressed public key).
host (:obj:`str`): the ip or hostname to connect to, optional.
host (:obj:`int`): the port to connect to, optional.
port (:obj:`int`): the port to connect to, optional.
config: (:obj:`dict`): the configuration dictionary.

Returns:
Expand Down
32 changes: 32 additions & 0 deletions watchtower-plugin/net/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,38 @@ def send_appointment(tower_id, tower, appointment_dict, signature):
return response


def get_request(endpoint):
"""
Sends a get request to the tower.

Args:
endpoint (:obj:`str`): the endpoint to send the post request.

Returns:
:obj:`Response`: a Response object with the obtained data.

Raises:
:obj:`ConnectionError`: if the client cannot connect to the tower.
:obj:`TowerResponseError`: if the returned status code is not a 200-family code.
"""

try:
response = requests.get(url=endpoint)

if response.ok:
return response
else:
raise TowerResponseError(
"The server returned an error", status_code=response.status_code, reason=response.reason, data=response,
)

except ConnectionError:
raise TowerConnectionError(f"Cannot connect to {endpoint}. Tower cannot be reached")

except (InvalidSchema, MissingSchema, InvalidURL):
raise TowerConnectionError(f"Invalid URL. No schema, or invalid schema, found (url={endpoint})")


def post_request(data, endpoint, tower_id):
"""
Sends a post request to the tower.
Expand Down
51 changes: 51 additions & 0 deletions watchtower-plugin/test_watchtower.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(self, tower_sk):

# Adds all the routes to the functions listed above.
routes = {
"/ping": (self.ping, ["GET"]),
"/register": (self.register, ["POST"]),
"/add_appointment": (self.add_appointment, ["POST"]),
"/get_appointment": (self.get_appointment, ["POST"]),
Expand All @@ -49,6 +50,14 @@ def __init__(self, tower_sk):
logging.getLogger("werkzeug").setLevel(logging.ERROR)
os.environ["WERKZEUG_RUN_MAIN"] = "true"

def ping(self):
if mocked_return == "online":
return ping_online()
elif mocked_return == "offline":
return ping_offline()
elif mocked_return == "error":
return ping_error()

def register(self):
user_id = request.get_json().get("public_key")

Expand Down Expand Up @@ -127,6 +136,18 @@ def add_appointment_success(appointment, signature, user, tower_sk):
return response, rcode


def ping_online():
return "", constants.HTTP_EMPTY


def ping_offline():
raise ConnectionError()


def ping_error():
return "", constants.HTTP_NOT_FOUND


def add_appointment_reject_no_slots():
# This covers non-registered users and users with no available slots

Expand Down Expand Up @@ -215,6 +236,36 @@ def test_helpme_starts(node_factory):
l1.start()


def test_watchtower_ping_offline(node_factory):
global mocked_return

mocked_return = "offline"

l1 = node_factory.get_node(options={"plugin": plugin_path})
r = l1.rpc.pingtower(tower_netaddr, tower_port)
assert r.get("alive") is False


def test_watchtower_ping_online(node_factory):
global mocked_return

mocked_return = "online"

l1 = node_factory.get_node(options={"plugin": plugin_path})
r = l1.rpc.pingtower(tower_netaddr, tower_port)
assert r.get("alive") is True


def test_watchtower_ping_error(node_factory):
global mocked_return

mocked_return = "error"

l1 = node_factory.get_node(options={"plugin": plugin_path})
r = l1.rpc.pingtower(tower_netaddr, tower_port)
assert r.get("alive") is False


def test_watchtower(node_factory):
""" Tests sending data to a single tower with short connection issue"""

Expand Down
36 changes: 35 additions & 1 deletion watchtower-plugin/watchtower.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from tower_info import TowerInfo
from towers_dbm import TowersDBM
from keys import generate_keys, load_keys
from net.http import post_request, process_post_response, add_appointment
from net.http import get_request, post_request, process_post_response, add_appointment


DATA_DIR = os.getenv("TOWERS_DATA_DIR", os.path.expanduser("~/.watchtower/"))
Expand Down Expand Up @@ -178,6 +178,40 @@ def init(options, configuration, plugin):
raise IOError(error)


@plugin.method("pingtower", desc="Pings a tower to check if its online")
def ping(plugin, host, port=None):
"""
Pings a tower to check if its online

Args:
plugin (:obj:`Plugin`): this plugin.
host (:obj:`str`): the ip or hostname to connect to.
port (:obj:`int`): the port to connect to, optional.
"""

response = {"alive": True}

try:
tower_netaddr = arg_parser.parse_ping_arguments(host, port, plugin.wt_client.config)

# Defaulting to http hosts for now
if not tower_netaddr.startswith("http"):
tower_netaddr = "http://" + tower_netaddr

# Send request to the server.
ping_endpoint = f"{tower_netaddr}/ping"
plugin.log(f"Pinging the Eye of Satoshi at {tower_netaddr}")
get_request(ping_endpoint)
plugin.log(f"The Eye of Satoshi is alive")

except (InvalidParameter, TowerConnectionError, TowerResponseError) as e:
plugin.log(str(e), level="warn")
response["alive"] = False
response["reason"] = str(e)

return response


@plugin.method("registertower", desc="Register your public key (user id) with the tower.")
def register(plugin, tower_id, host=None, port=None):
"""
Expand Down