aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWei-Ning Huang <w@byzantine-lab.io>2019-08-29 20:58:07 +0800
committerWei-Ning Huang <w@byzantine-lab.io>2019-09-17 16:57:31 +0800
commit266e3da512e3327f78c3ada3815e9c353698ccc6 (patch)
tree0ecd1975923383c6fe283e5df95994fa178834ee
parent4db99d21b706bb767f0d4aae505da3893276d843 (diff)
downloadgo-tangerine-266e3da512e3327f78c3ada3815e9c353698ccc6.tar
go-tangerine-266e3da512e3327f78c3ada3815e9c353698ccc6.tar.gz
go-tangerine-266e3da512e3327f78c3ada3815e9c353698ccc6.tar.bz2
go-tangerine-266e3da512e3327f78c3ada3815e9c353698ccc6.tar.lz
go-tangerine-266e3da512e3327f78c3ada3815e9c353698ccc6.tar.xz
go-tangerine-266e3da512e3327f78c3ada3815e9c353698ccc6.tar.zst
go-tangerine-266e3da512e3327f78c3ada3815e9c353698ccc6.zip
scripts: add scripts for running bp nodes
-rwxr-xr-xscripts/run_bp.py229
1 files changed, 229 insertions, 0 deletions
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 ...')