From 266e3da512e3327f78c3ada3815e9c353698ccc6 Mon Sep 17 00:00:00 2001
From: Wei-Ning Huang <w@byzantine-lab.io>
Date: Thu, 29 Aug 2019 20:58:07 +0800
Subject: scripts: add scripts for running bp nodes

---
 scripts/run_bp.py | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 229 insertions(+)
 create mode 100755 scripts/run_bp.py

(limited to 'scripts')

diff --git a/scripts/run_bp.py b/scripts/run_bp.py
new file mode 100755
index 000000000..72022110f
--- /dev/null
+++ b/scripts/run_bp.py
@@ -0,0 +1,229 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 The go-tangerine Authors
+# This file is part of the go-tangerine library.
+#
+# The go-tangerine library is free software: you can redistribute it
+# and/or modify it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation, either version 3 of the License,
+# or (at your option) any later version.
+#
+# The go-tangerine library is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with the go-tangerine library. If not, see
+# <http://www.gnu.org/licenses/>.
+
+
+import argparse
+import hashlib
+import json
+import os
+import platform
+import random
+import re
+import socket
+import subprocess
+import sys
+import time
+
+
+try:
+    import ntplib
+except Exception:
+    print('Please run `pip3 install ntplib\'')
+    sys.exit(1)
+
+
+CONTAINER_NAME = 'tangerine'
+NUM_SLOTS = 5
+POLLING_INTERVAL = 1
+SLEEP_RAND_RANGE = 1800
+TANGERINE_IMAGE_TMPL = 'byzantinelab/go-tangerine:latest-%s-%d'
+TOOLS_IMAGE = 'byzantinelab/tangerine-tools'
+WHITELISTED_FILES = [
+    os.path.basename(sys.argv[0]),
+    'datadir',
+    'node.key'
+]
+
+
+def get_shard_id(nodekey):
+    """Return shard ID.
+
+    Shard ID is calculate as (first byte of sha3 of Node Key address) mode NUM_SLOTS.
+    """
+    wd = os.getcwd()
+    p = subprocess.Popen(['docker', 'run', '-v', '%s:/mnt' % wd, '-t', TOOLS_IMAGE,
+                          'nodekey', 'inspect', '/mnt/%s' % nodekey],
+                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    stdout, stderr = p.communicate()
+
+    address = re.search('^Node Address: (0x[0-9a-fA-F]{40})', stdout.decode('utf-8')).group(1)
+    return int(hashlib.sha256(address.encode('utf-8')).hexdigest()[0], base=16) % NUM_SLOTS
+
+
+def get_tangerine_image(args):
+    """Return the tangerine image by shard-ID."""
+    return TANGERINE_IMAGE_TMPL % ('testnet' if args.testnet else 'mainnet',
+                                   get_shard_id(args.nodekey))
+
+
+def get_time_delta():
+    """Compare time with NTP and return time delta."""
+    c = ntplib.NTPClient()
+    response = c.request('tw.pool.ntp.org', version=3)
+    return response.offset
+
+
+def generate_node_key(nodekey):
+    """Generate a new node key."""
+    if os.path.exists(nodekey):
+        raise RuntimeError('node.key already exists, abort.')
+
+    wd = os.getcwd()
+    subprocess.Popen(['docker', 'run', '-v', '%s:/mnt' % wd, '-t', TOOLS_IMAGE,
+                      'nodekey', 'generate', '/mnt/%s' % nodekey]).wait()
+
+    print('Node key generated')
+    print('\n\033[5;91;49mPlease backup node.key in a secure place!\033[0m')
+    print('\n\033[0;91mSend at least 1 ETH (use Rinkeby ETH for testnet) to node key address!\033[0m')
+    print('\033[0;91mThese ETHs are used for then Tangerine network recovery mechanism.\033[0m\n\n')
+
+
+def get_image_version(image):
+    """Get a docker image's version."""
+    p = subprocess.Popen(['docker', 'inspect', '-f', '{{.Id}}', image],
+            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    stdout, stderr = p.communicate()
+    return stdout
+
+
+def update_image(image):
+    """Update a given docker image."""
+    subprocess.Popen(['docker', 'pull', image],
+                     stdout=subprocess.PIPE, stderr=subprocess.PIPE).wait()
+
+
+def check_environment():
+    """Check execution environment."""
+    for f in os.listdir(os.getcwd()):
+        if f not in WHITELISTED_FILES:
+            raise RuntimeError('please execute this script in an empty directory, abort')
+
+    if get_time_delta() > 0.05:
+        raise RuntimeError('please sync your network time by installing a NTP client')
+
+    if platform.system() == 'Linux':
+        p1 = subprocess.Popen('ps aux | grep -q "[n]tp"', shell=True).wait()
+        p2 = subprocess.Popen('ps aux | grep -q "[c]hrony"', shell=True).wait()
+        if p1 != 0 and p2 != 0:
+            raise RuntimeError('please install ntpd or chrony to synchronize system time')
+
+
+def start(args, force=False):
+    """Start the docker container."""
+    p = subprocess.Popen(['docker', 'inspect', '-f', '{{.State.Running}}', CONTAINER_NAME],
+                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    stdout, stderr = p.communicate()
+
+    if stdout.strip() == b'true':
+        if force:
+            print('Stopping old container ...')
+            subprocess.Popen(['docker', 'rm', '-f', CONTAINER_NAME]).wait()
+        else:
+            print('Container already running.')
+            return
+    elif stdout.strip() == b'false':
+        print('Stopping old container ...')
+        subprocess.Popen(['docker', 'rm', '-f', CONTAINER_NAME]).wait()
+
+    tangerine_image = get_tangerine_image(args)
+
+    wd = os.getcwd()
+    cmd = ['docker', 'run',
+           '-d', '--name=%s' % CONTAINER_NAME,
+           '-v', '%s:/mnt' % wd,
+           '-p', '30303:30303/tcp',
+           '-p', '30303:30303/udp',
+           '--restart', 'always',
+           '-t', tangerine_image,
+           '--identity=%s' % args.identity,
+           '--bp',
+           '--nodekey=/mnt/%s' % args.nodekey,
+           '--datadir=/mnt/datadir',
+           '--syncmode=fast',
+           '--cache=1024',
+           '--verbosity=3',
+           '--gcmode=archive']
+
+    if args.testnet:
+        cmd.append('--testnet')
+
+    print('Starting container ...')
+    update_image(tangerine_image)
+    subprocess.Popen(cmd, stdout=subprocess.PIPE).wait()
+
+    print('\nContainer running, check logs with `docker logs -f %s\'\n' % CONTAINER_NAME)
+
+
+def monitor(args):
+    """Monitor if there are newer image."""
+    tangerine_image = get_tangerine_image(args)
+    old_image = get_image_version(tangerine_image)
+
+    while True:
+        update_image(tangerine_image)
+        new_image = get_image_version(tangerine_image)
+
+        if new_image != old_image:
+            sleep_time = random.randint(0, SLEEP_RAND_RANGE)
+            print('New image found, sleeping for %s seconds before updating ...' % sleep_time)
+            time.sleep(sleep_time)
+
+            # Check update again.
+            update_image(tangerine_image)
+            new_image = get_image_version(tangerine_image)
+            start(args, True)
+
+            old_image = new_image
+
+        time.sleep(POLLING_INTERVAL)
+
+
+def main():
+    """Main."""
+    parser = argparse.ArgumentParser(description='Script for launching a Tangerine Node')
+    parser.add_argument('--nodekey', default='node.key', dest='nodekey',
+                        help='Path to nodekey, default to `node.key\'')
+    parser.add_argument('--identity', default=socket.gethostname(),
+                        dest='identity', help='Name of the node, e.g. ByzantineLab')
+    parser.add_argument('--testnet', default=False, action='store_true', dest='testnet',
+                        help='Whether or not to run a testnet node')
+
+    args = parser.parse_args()
+
+    check_environment()
+
+    if not os.path.exists(args.nodekey):
+        res = input('No node key found, generate a new one? [y/N] ')
+        if res == 'y':
+            generate_node_key()
+        else:
+            print('Abort.')
+            sys.exit(1)
+
+    start(args)
+    monitor(args)
+
+
+if __name__ == '__main__':
+    try:
+        main()
+    except RuntimeError as e:
+        print('Error: %s' % str(e))
+    except KeyboardInterrupt:
+        print('Got interrupt, quitting ...')
-- 
cgit v1.2.3