From 24aa2a0c92cb4fb86d9ed8845d92194367f8db0b Mon Sep 17 00:00:00 2001
From: felixsidokhine <felix.sidokhine@savoirfairelinux.com>
Date: Tue, 5 Mar 2019 14:30:22 -0500
Subject: [PATCH] nameservice: public-key and signature now stored on
 blockchain

The public-key and signature can now be POSTed at time of registration
These are validated by server before posting to blockchain, and can be
retrieved by users for validation on their local machines as well

Added appropriate HTTP codes as well

Modified contract to store and retrieve these values and various scripts
(server, batch reading/writing) to handle these new values

Change-Id: Iddcaa00ab0d7068cea6d578489eacb89e8354b7c
---
 contract/registrar.sol |  76 ++++++------
 index.js               | 255 ++++++++++++++++++++++++++++++++---------
 start_eth_cluster.py   |  90 ---------------
 3 files changed, 244 insertions(+), 177 deletions(-)
 delete mode 100644 start_eth_cluster.py

diff --git a/contract/registrar.sol b/contract/registrar.sol
index ed9a25f..2ca7910 100644
--- a/contract/registrar.sol
+++ b/contract/registrar.sol
@@ -1,4 +1,4 @@
-pragma solidity ^0.4.0;
+pragma solidity ^0.5.2;
 /*
  * Copyright (c) 2014 Gav Wood <g@ethdev.com>
  * Copyright (c) 2016 Savoir-faire Linux Inc.
@@ -26,24 +26,26 @@ pragma solidity ^0.4.0;
  */
 
 contract NameRegister {
-  function addr(bytes32 _name) constant returns (address o_owner) {}
-  function name(address _owner) constant returns (bytes32 o_name) {}
+  function addr(bytes32 _name) public view returns (address o_owner) {}
+  function name(address _owner) public view returns (bytes32 o_name) {}
 }
 
 contract Registrar is NameRegister {
   event Changed(bytes32 indexed name);
   event PrimaryChanged(bytes32 indexed name, address indexed addr, address owner);
 
-  function owner(bytes32 _name) constant returns (address o_owner) {}
-  function addr(bytes32 _name) constant returns (address o_address) {}
-  function subRegistrar(bytes32 _name) constant returns (address o_subRegistrar) {}
-  function content(bytes32 _name) constant returns (bytes32 o_content) {}
+  function owner(bytes32 _name) public view returns (address o_owner) {}
+  function addr(bytes32 _name) public view returns (address o_address) {}
+  function subRegistrar(bytes32 _name) public view returns (address o_subRegistrar) {}
+  function content(bytes32 _name) public view returns (bytes32 o_content) {}
 
-  function name(address _owner) constant returns (bytes32 o_name) {}
+  function name(address _owner) public view returns (bytes32 o_name) {}
 }
 
 contract GlobalRegistrar is Registrar {
   struct Record {
+    string publicKey;
+    string signedName;
     address owner;
     address primary;
     address subRegistrar;
@@ -52,68 +54,70 @@ contract GlobalRegistrar is Registrar {
     uint renewalDate;
   }
 
-  function Registrar() {
-  }
-
-  function reserve(bytes32 _name, address _a) {
-    if (m_toRecord[_name].owner == 0 && m_toName[_a] == 0) {
+  function reserve(bytes32 _name, address _a) public {
+    if (m_toRecord[_name].owner == address(0) && m_toName[_a] == 0) {
       m_toRecord[_name].owner = msg.sender;
       m_toRecord[_name].primary = _a;
       m_toName[_a] = _name;
-      Changed(_name);
-      PrimaryChanged(_name, _a, msg.sender);
+      emit Changed(_name);
+      emit PrimaryChanged(_name, _a, msg.sender);
     }
   }
-  function reserveFor(bytes32 _name, address _owner, address _a) {
-    if (m_toRecord[_name].owner == 0 && m_toName[_a] == 0) {
+  function reserveFor(bytes32 _name, address _owner, address _a, string memory _publickey, string memory _signedname) public {
+    if (m_toRecord[_name].owner == address(0) && m_toName[_a] == 0) {
       m_toRecord[_name].owner = _owner;
       m_toRecord[_name].primary = _a;
+      m_toRecord[_name].publicKey = _publickey;
+      m_toRecord[_name].signedName = _signedname;
+
       m_toName[_a] = _name;
-      Changed(_name);
-      PrimaryChanged(_name, _a, _owner);
+      emit Changed(_name);
+      emit PrimaryChanged(_name, _a, _owner);
     }
   }
 
   modifier onlyrecordowner(bytes32 _name) { if (m_toRecord[_name].owner == msg.sender) _; }
 
-  function transfer(bytes32 _name, address _newOwner) onlyrecordowner(_name) {
+  function transfer(bytes32 _name, address _newOwner) public onlyrecordowner(_name) {
     m_toRecord[_name].owner = _newOwner;
-    Changed(_name);
+    emit Changed(_name);
   }
 
-  function disown(bytes32 _name) onlyrecordowner(_name) {
+  function disown(bytes32 _name) public onlyrecordowner(_name) {
     if (m_toName[m_toRecord[_name].primary] == _name)
     {
-      PrimaryChanged(_name, m_toRecord[_name].primary, m_toRecord[_name].owner);
+      emit PrimaryChanged(_name, m_toRecord[_name].primary, m_toRecord[_name].owner);
       m_toName[m_toRecord[_name].primary] = "";
     }
     delete m_toRecord[_name];
-    Changed(_name);
+    emit Changed(_name);
   }
 
-  function setAddress(bytes32 _name, address _a, bool _primary) onlyrecordowner(_name) {
+  function setAddress(bytes32 _name, address _a, bool _primary) public onlyrecordowner(_name) {
     m_toRecord[_name].primary = _a;
     if (_primary)
     {
-      PrimaryChanged(_name, _a, m_toRecord[_name].owner);
+      emit PrimaryChanged(_name, _a, m_toRecord[_name].owner);
       m_toName[_a] = _name;
     }
-    Changed(_name);
+    emit Changed(_name);
   }
-  function setSubRegistrar(bytes32 _name, address _registrar) onlyrecordowner(_name) {
+  function setSubRegistrar(bytes32 _name, address _registrar) public onlyrecordowner(_name) {
     m_toRecord[_name].subRegistrar = _registrar;
-    Changed(_name);
+    emit Changed(_name);
   }
-  function setContent(bytes32 _name, bytes32 _content) onlyrecordowner(_name) {
+  function setContent(bytes32 _name, bytes32 _content) public onlyrecordowner(_name) {
     m_toRecord[_name].content = _content;
-    Changed(_name);
+    emit Changed(_name);
   }
 
-  function owner(bytes32 _name) constant returns (address) { return m_toRecord[_name].owner; }
-  function addr(bytes32 _name) constant returns (address) { return m_toRecord[_name].primary; }
-  function register(bytes32 _name) constant returns (address) { return m_toRecord[_name].subRegistrar; }
-  function content(bytes32 _name) constant returns (bytes32) { return m_toRecord[_name].content; }
-  function name(address _a) constant returns (bytes32 o_name) { return m_toName[_a]; }
+  function owner(bytes32 _name) public view returns (address) { return m_toRecord[_name].owner; }
+  function addr(bytes32 _name) public view returns (address) { return m_toRecord[_name].primary; }
+  function register(bytes32 _name) public view returns (address) { return m_toRecord[_name].subRegistrar; }
+  function content(bytes32 _name) public view returns (bytes32) { return m_toRecord[_name].content; }
+  function name(address _a) public view returns (bytes32 o_name) { return m_toName[_a]; }
+  function publickey(bytes32 _name) public view returns (string memory) { return m_toRecord[_name].publicKey; }
+  function signature(bytes32 _name) public view returns (string memory) { return m_toRecord[_name].signedName; }
 
   mapping (address => bytes32) m_toName;
   mapping (bytes32 => Record) m_toRecord;
diff --git a/index.js b/index.js
index 541c429..caf1717 100755
--- a/index.js
+++ b/index.js
@@ -18,15 +18,38 @@
  */
 'use strict';
 
-var express = require('express');
-var bodyParser = require('body-parser');
-var BigNumber = require('bignumber.js');
-var fs = require('fs');
-var http = require('http');
-var https = require('https');
-var Web3 = require('web3');
-var web3 = new Web3();
-var argv = require('minimist')(process.argv.slice(2));
+const express = require('express');
+const bodyParser = require('body-parser');
+const BigNumber = require('bignumber.js');
+const fs = require('fs');
+const http = require('http');
+const https = require('https');
+const Web3 = require('web3');
+const web3 = new Web3();
+const argv = require('minimist')(process.argv.slice(2));
+const crypto = require('crypto');
+const path = require('path');
+
+//Patch to support caching.
+//map of form {name,address}
+const cache = {};
+
+function validateFile(filename){
+    if ( path.isAbsolute(filename) && fs.existsSync(filename) )
+        return filename
+    else if ( !path.isAbsolute(filename) && fs.existsSync("./" +filename))
+        return path.resolve(filename)
+    return false
+}
+
+function loadCache(batchInputFile) {
+    const NAME_LIST = JSON.parse(fs.readFileSync(batchInputFile, 'utf8'));
+    for (const entry of Object.entries(NAME_LIST)) {
+        cache[entry[0]] = entry[1]
+    }
+}
+
+
 
 Object.getPrototypeOf(web3.eth).awaitConsensus = function(txhash, mined_cb) {
     var ethP = this;
@@ -46,25 +69,45 @@ Object.getPrototypeOf(web3.eth).awaitConsensus = function(txhash, mined_cb) {
 }
 
 web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545'));
-var coinbase = web3.eth.coinbase;
+const coinbase = web3.eth.coinbase;
 console.log(coinbase);
-var balance = web3.eth.getBalance(coinbase);
+let balance = web3.eth.getBalance(coinbase);
 console.log(balance.toString(10));
 
-var REG_FILE = __dirname + "/contract/registrar.out.json";
-var REG_ADDR_FILE = __dirname + "/contractAddress.txt";
-var NAME_VALIDATOR = new RegExp('^[a-z0-9-_]{3,32}$');
+const REG_FILE = __dirname + "/contract/registrar.out.json";
+const REG_ADDR_FILE = __dirname + "/contractAddress.txt";
+const NAME_VALIDATOR = new RegExp('^[a-z0-9-_]{3,32}$');
 
-var account;
-var regAddress = "0xe53cb2ace8707526a5050bec7bcf979c57f8b44f";
-var regData;
-var regContract;
-var reg;
+let account;
+let regAddress = "0xe53cb2ace8707526a5050bec7bcf979c57f8b44f";
+let regData;
+let regContract;
+let reg;
 
-const cache = {};
+
+function loadNames(filename){
+    console.log("The cache will be populated with the data from the export file!");
+    const providedPath = String(argv['_'][0]);
+    const batchInputFile = validateFile(providedPath);
+    if(!batchInputFile){
+        throw "File " + providedPath + " does not exist";
+    }
+    else{
+        loadCache(batchInputFile);
+    }
+}
+
+
+function verifySignature(name, _publickey, signature){
+    const publicKey = new Buffer(_publickey, 'base64').toString('ascii')
+    const verifier = crypto.createVerify('RSA-SHA512');
+    verifier.update(name);
+    const ver = verifier.verify(publicKey, signature,'base64');
+    return ver;
+}
 
 function unlockAccount() {
-    web3.personal.unlockAccount(coinbase, "toto");
+    web3.personal.unlockAccount(coinbase, "apple123");
 }
 
 function getRemainingGaz() {
@@ -76,8 +119,8 @@ function waitForGaz(want, cb) {
         cb();
         return;
     }
-    var timeout = function() {
-        var g = getRemainingGaz();
+    const timeout = () => {
+        const g = getRemainingGaz();
         if (g >= want) {
             //web3.miner.stop();
             console.log("Mining finished ! Now having " + g + " gaz.");
@@ -91,8 +134,8 @@ function waitForGaz(want, cb) {
     timeout();
 }
 
-function loadContract() {
-    fs.readFile(REG_ADDR_FILE, function(err, content) {
+function loadContract(onContractLoaded) {
+    fs.readFile(REG_ADDR_FILE, (err, content) => {
         if (err) {
             console.log("Can't read contract address: " + err);
         } else {
@@ -111,13 +154,13 @@ function loadContract() {
                     console.log("Error getting contract code: " + error);
                 if (!result || result == "0x") {
                     console.log("Contract not found at " + regAddress);
-                    initContract();
+                    initContract(onContractLoaded);
                 } else {
                     regContract.at(regAddress, function(err, result) {
                         console.log("Contract found and loaded from " + regAddress);
                         if(!err) {
                             reg = result;
-                            startServer();
+                            onContractLoaded(reg)
                         }
                         else {
                             console.error("err: " + err);
@@ -129,7 +172,7 @@ function loadContract() {
     });
 }
 
-function initContract() {
+function initContract(onContractInitialized) {
     waitForGaz(3000000, function(){
         regContract.new({ from: coinbase,
                           data: '0x'+regData.evm.bytecode.object,
@@ -142,7 +185,7 @@ function initContract() {
                     regAddress = contract.address;
                     fs.writeFileSync(REG_ADDR_FILE, regAddress);
                     reg = contract;
-                    startServer();
+                    onContractInitialized();
                 }
             } else {
                 console.log(e);
@@ -173,7 +216,7 @@ function parseString(s) {
 
 function formatAddress(address) {
     if (address) {
-        var s = address.trim();
+        let s = address.trim();
         try {
             if (s.startsWith("ring:"))
                 s = s.substr(5);
@@ -188,8 +231,8 @@ function formatAddress(address) {
 }
 
 function readCertificateChain(path) {
-    var cert = [];
-    var ca = [];
+    let cert = [];
+    const ca = [];
     fs.readFileSync(path, 'utf8').split("\n").forEach(function(line) {
         cert.push(line);
         if (line.match(/-END CERTIFICATE-/)) {
@@ -200,9 +243,9 @@ function readCertificateChain(path) {
     return ca;
 }
 
-function startServer() {
+function startServer(result) {
     console.log("Starting web server");
-    var app = express();
+    const app = express();
     app.disable('x-powered-by');
     app.use(bodyParser.json());
     app.use(function(req, res, next) {
@@ -213,19 +256,94 @@ function startServer() {
     // Register name lookup handler
     app.get("/name/:name", function(req, http_res) {
         try {
-            reg.addr(formatName(req.params.name), function(err, res) {
+            reg.addr(formatName(req.params.name), function(err, res_addr) {
+                try {
+                    if (err)
+                        console.log("Name lookup error: " + err);
+                    if (isHashZero(res_addr)) {
+                        throw Error("name not registered");
+                        //http_res.status(404).end(JSON.stringify({"error": "name not registered"}));
+                    } else {
+                        reg.publickey(formatName(req.params.name), function(err, res_publickey) {
+                            try {
+                                if (err)
+                                    console.log("Name lookup error: " + err);
+                                if (isHashZero(res_publickey)) {
+                                    http_res.end(JSON.stringify({"name": req.params.name, "addr": res_addr}));
+                                } else {
+                                    reg.signature(formatName(req.params.name), function(err, res_signature) {
+                                        try {
+                                            if (err)
+                                                console.log("Name lookup error: " + err);
+                                            if (isHashZero(res_signature)) {
+                                                http_res.end(JSON.stringify({"name": req.params.name, "addr": res_addr}));
+                                            } else {
+                                                http_res.end(JSON.stringify({"name": req.params.name, "addr": res_addr, "publickey": res_publickey, "signature": res_signature }));
+                                            }
+                                        } catch (err) {
+                                            console.log("Name lookup exception: " + err);
+                                            http_res.status(500).end(JSON.stringify({"error": "server error"}));
+                                        }
+                                    });
+                                }
+                            } catch (err) {
+                                console.log("Name lookup exception: " + err);
+                                http_res.status(500).end(JSON.stringify({"error": "server error"}));
+                            }
+                        });
+                    }
+                } catch (err) {
+                    if(cache[req.params.name] != undefined){
+                            if(cache[req.params.name]['publickey'] && cache[req.params.name]['signature']){
+                                http_res.end(JSON.stringify({"name": req.params.name, "addr": cache[req.params.name]['addr'], "publickey": cache[req.params.name]['publickey'], "signature": cache[req.params.name]['signature']}));
+                            }
+                            else{
+                                http_res.end(JSON.stringify({"name": req.params.name, "addr": cache[req.params.name]['addr']}));
+                            }
+                    }
+                    else{
+                        http_res.status(404).end(JSON.stringify({"error": "name not registered"}));    
+                    }
+                }
+            });
+        } catch (err) {
+            console.log("Name lookup exception: " + err);
+            http_res.status(500).end(JSON.stringify({"error": "server error"}));
+        }
+    });
+
+    app.get("/name/:name/publickey", function(req, http_res) {
+        try {
+            reg.publickey(formatName(req.params.name), function(err, res) {
                 try {
                     if (err)
                         console.log("Name lookup error: " + err);
                     if (isHashZero(res)) {
-                        const cachedAddress = cache[req.params.name];
-                        if (cachedAddress != undefined) {
-                            http_res.end(JSON.stringify({"name": req.params.name,"addr": cachedAddress}));
-                        } else {
-                            http_res.status(404).end(JSON.stringify({"error": "name not registred"}));
-                        }
+                        http_res.status(404).end(JSON.stringify({"error": "name not registered"}));
+                    } else {
+                        http_res.end(JSON.stringify({"name": req.params.name, "publickey": res }));
+                    }
+                } catch (err) {
+                    console.log("Name lookup exception: " + err);
+                    http_res.status(500).end(JSON.stringify({"error": "server error"}));
+                }
+            });
+        } catch (err) {
+            console.log("Name lookup exception: " + err);
+            http_res.status(500).end(JSON.stringify({"error": "server error"}));
+        }
+    });
+
+    app.get("/name/:name/signature", function(req, http_res) {
+        try {
+            reg.signature(formatName(req.params.name), function(err, res) {
+                try {
+                    if (err)
+                        console.log("Name lookup error: " + err);
+                    if (isHashZero(res)) {
+                        http_res.status(404).end(JSON.stringify({"error": "name not registered"}));
                     } else {
-                        http_res.end(JSON.stringify({"name": req.params.name,"addr": res}));
+                        http_res.end(JSON.stringify({"name": req.params.name, "signature": res }));
                     }
                 } catch (err) {
                     console.log("Name lookup exception: " + err);
@@ -246,15 +364,14 @@ function startServer() {
                     if (err)
                         console.log("Owner lookup error: " + err);
                     if (isHashZero(res)) {
-                        http_res.status(404).end(JSON.stringify({"error": "name not registred"}));
+                        http_res.status(404).end(JSON.stringify({"error": "name not registered"}));
                     } else {
-                        http_res.end(JSON.stringify({"name": req.params.name,"owner": res}));
+                        http_res.end(JSON.stringify({"name": req.params.name, "owner": res}));
                     }
                 } catch (err) {
                     console.log("Owner lookup exception: " + err);
                     http_res.status(500).end(JSON.stringify({"error": "server error"}));
                 }
-                //http_res.end(JSON.stringify({"name": req.params.name,"owner": res}));
             });
         } catch (err) {
             console.log("Owner lookup exception: " + err);
@@ -279,7 +396,7 @@ function startServer() {
                     if (name)
                         http_res.end(JSON.stringify({"name": name}));
                     else
-                        http_res.status(404).end(JSON.stringify({"error": "address not registred"}));
+                        http_res.status(404).end(JSON.stringify({"error": "address not registered"}));
                 } catch (err) {
                     console.log("Address lookup exception: " + err);
                     http_res.status(500).end(JSON.stringify({"error": "server error"}));
@@ -313,8 +430,36 @@ function startServer() {
                 http_res.status(400).end(JSON.stringify({"success": false, "error": "invalid name"}));
                 return;
             }
+            if(cache[req.params.name] == undefined){
+                http_res.status(400).end(JSON.stringify({"success":false,"error": "name already registered"}));
+                return;
+            }
+            //Temporarily commented out for testing purposes.
+            //Backward compatibility patch to allow registrations without public keys:
+            let publickey;
+            let signature;
+            if(!req.body.publickey && !req.body.signature){
+                publickey = 0;
+                signature = 0;
+            }
+            else{
+                if (!req.body.publickey || req.body.publickey == "") {
+                    http_res.status(400).end(JSON.stringify({"success": false, "error": "publickey not found or invalid"}));
+                    return;
+                }
+                if (!req.body.signature || req.body.signature == "") {
+                    http_res.status(400).end(JSON.stringify({"success": false, "error": "signature not found or invalid"}));
+                }
+                if(!verifySignature(req.params.name, req.body.publickey, req.body.signature)){
+                    http_res.status(401).end(JSON.stringify({"success": false, "error": "signature verification failed"}));
+                    return;
+                }
+                else{
+                    publickey = req.body.publickey;
+                    signature = req.body.signature;
+                }
+            }
             console.log("Got reg request (" + req.params.name + " -> " + addr + ") from " + req.body.owner);
-
             reg.owner(req.params.name, function(err, owner) {
                 if (owner == 0) {
                     reg.name(addr, function(err, res) {
@@ -328,7 +473,7 @@ function startServer() {
                             } else {
                                 console.log("Remaing gaz: " + getRemainingGaz());
                                 unlockAccount();
-                                reg.reserveFor.sendTransaction(formatName(req.params.name), req.body.owner, addr, {
+                                reg.reserveFor.sendTransaction(formatName(req.params.name), req.body.owner, addr, publickey, signature, {
                                     from: coinbase,
                                     gas: 3000000
                                 }, function(terr, reg_c) {
@@ -336,16 +481,21 @@ function startServer() {
                                         console.log("Transaction error " + JSON.stringify(terr));
                                         http_res.end(JSON.stringify(terr));
                                     } else {
+                                        //Add the registration into the cache.
+                                        cache[req.params.name] = {
+                                            addr,
+                                            publickey,
+                                            signature
+                                        };
+                                        //Now we continue with the sending of the transactions.
                                         console.log("Transaction sent " + reg_c);
                                         // Send answer as soon as the transaction is queued
-                                        cache[req.params.name] = addr;
                                         http_res.end(JSON.stringify({"success": true}));
                                         web3.eth.awaitConsensus(reg_c, function(error) {
                                             if (error) {
                                                 console.log(error);
                                                 return;
                                             }
-                                            delete cache[req.params.name];
                                             console.log("Ended registration for " + req.params.name + " -> " + addr);
                                         });
                                     }
@@ -394,5 +544,8 @@ function startServer() {
     }
 }
 
+if(argv['_'] != 0){
+    loadNames(argv['_']);  
+}
 unlockAccount();
-loadContract();
+loadContract(startServer);
diff --git a/start_eth_cluster.py b/start_eth_cluster.py
deleted file mode 100644
index 44b8c07..0000000
--- a/start_eth_cluster.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/usr/bin/python3
-import os, sys
-import time
-import argparse
-import subprocess as proc
-import libtmux
-import re
-
-parser = argparse.ArgumentParser(description='Launch Ethereum test nodes in screen')
-parser.add_argument('--geth', '-e', default='geth', help='geth executable')
-parser.add_argument('--start-port', '-p', type=int, default=4230, help='inital port')
-parser.add_argument('--nodes', '-n', type=int, default=8, help='number of nodes')
-parser.add_argument('--netid', '-id', type=int, default=4226, help='Ethereum network ID')
-parser.add_argument('--datadir', '-d', help='Data directory')
-parser.add_argument('--genesis', '-g', help='Genesis file', default='genesis.json')
-args = parser.parse_args()
-
-data_dir = args.datadir
-if not data_dir:
-    data_dir = os.path.expanduser("~/eth_net_{}/".format(args.netid))
-print("Using directory", data_dir)
-
-passwd = data_dir+'password'
-try:
-    os.makedirs(data_dir)
-    with open(passwd,'w') as f:
-        f.write('toto')
-except Exception as e:
-    pass
-
-print("Creating {} nodes.".format(args.nodes))
-for x in range(0, args.nodes):
-    ddir = data_dir + "node{}".format(x)
-    try:
-        os.makedirs(ddir)
-        cmd = '{} --datadir {} --networkid "{}" --identity "Node{}"  init {}'.format(args.geth, ddir, args.netid, x, args.genesis)
-        print(cmd)
-        proc.run(cmd, shell=True)
-        cmd = '{} --datadir {} --networkid "{}" --identity "Node{}" --password {} account new'.format(args.geth, ddir, args.netid, x, passwd)
-        print(cmd)
-        proc.run(cmd, shell=True)
-    except Exception as e:
-        print(e)
-
-enode_finder = re.compile(b"enode://[0-9a-zA-Z]+@.+:\d+")
-enodes = []
-try:
-    for x in range(0, args.nodes):
-        ddir = data_dir + "node{}".format(x)
-        cmd = '{} --datadir {} --networkid "{}" --identity "Node{}" --port {} console'.format(args.geth, ddir, args.netid, x, args.start_port+x)
-        print(cmd)
-        with proc.Popen(cmd, shell=True, stdout=proc.PIPE, stderr=proc.PIPE) as p:
-            while True:
-                l = p.stderr.readline()
-                if l != '':
-                    res = enode_finder.search(l);
-                    if res:
-                        enodes.append(res.group(0))
-                        break
-                else:
-                    break
-except Exception as e:
-    print(e)
-
-
-for enode in enodes:
-    print(enode)
-
-bootnodes = b",".join(enodes).decode().replace('[::]', '[::1]')
-
-ts = libtmux.Server()
-
-screen_name = "eth{}".format(args.start_port)
-print("Launching {} nodes in tmux session {}.".format(args.nodes, screen_name))
-s = ts.new_session(screen_name)
-
-for x in range(0, args.nodes):
-    nm = "node{}".format(x)
-    w = s.new_window(nm)
-    p = w.attached_pane
-    ddir = data_dir + nm
-    suppargs = ''
-    #if x >= args.nodes/2:
-    #    suppargs += '--minerthreads "1" --mine '
-    if x == 0:
-        suppargs += '--rpc --rpcapi "personal,eth,net,web3" '
-    cmd = '{} --datadir {} -networkid "{}" --identity "{}" --port {} --bootnodes "{}" {} --verbosity "3" console'.format(args.geth, ddir, args.netid, nm, args.start_port+x, bootnodes, suppargs)
-    print(cmd)
-    p.send_keys(cmd)
-    time.sleep(1)
-- 
GitLab