[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"es2015\", \"stage-2\"]\n}"
  },
  {
    "path": ".dockerignore",
    "content": ".idea/\nnode_modules/\ntemp/\ndata/\nbuilds/\nconfig.ini\n*.iml\n.git\n\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea/\nnode_modules/\ntemp/\ndata/\nbuilds/\nconfig.ini\n*.iml\n\n.history/\n"
  },
  {
    "path": ".gitlab-ci.yml",
    "content": "image: docker:latest\n\nservices:\n  - docker:dind\n\nstages:\n  - test\n  - release\n\ncache:\n  paths:\n  - node_modules/\n\nbefore_script:\n  - apk update\n  - apk add jq yarn\n  - yarn install\n\ntest:\n  stage: test\n  script:\n    - yarn run test\n\nrelease:npm:\n  stage: release\n  only:\n    - /v\\d*\\.\\d*\\.\\d*/\n  except:\n    - branches\n  script:\n    # build the package\n    - yarn run make\n    \n    # publish on npm\n    - echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}'>.npmrc\n    - npm publish\n\nrelease:docker.io:\n  stage: release\n  only:\n    - /v\\d*\\.\\d*\\.\\d*/\n  except:\n    - branches\n  script:\n    # build the package\n    - yarn run make\n\n    # export the current version from package.json into a variable\n    - export PACKAGE_VERSION=$(cat package.json | jq -r .version)\n\n    # login to official docker registry\n    - docker login -u \"$DOCKER_REGISTRY_USER\" -p \"$DOCKER_REGISTRY_PASSWORD\"\n\n    # push tagged with version\n    - docker build --pull -t \"$DOCKER_REGISTRY_IMAGE:$PACKAGE_VERSION\" .\n    - docker push \"$DOCKER_REGISTRY_IMAGE:$PACKAGE_VERSION\"\n\n    # push tagged with latest\n    - docker build --pull -t \"$DOCKER_REGISTRY_IMAGE\" .\n    - docker push \"$DOCKER_REGISTRY_IMAGE\"\n\nrelease:gitlab:\n  stage: release\n  only:\n    - /v\\d*\\.\\d*\\.\\d*/\n  except:\n    - branches\n  script:\n    # build the package\n    - yarn run make\n\n    # export the current version from package.json into a variable\n    - export PACKAGE_VERSION=$(cat package.json | jq -r .version)\n\n    # publish on the official docker hub registry\n    - docker login -u \"$CI_REGISTRY_USER\" -p \"$CI_REGISTRY_PASSWORD\" $CI_REGISTRY\n\n    # push tagged with version\n    - docker build --pull -t \"$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG\" .\n    - docker push \"$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG\"\n\n    # push tagged with latest\n    - docker build --pull -t \"$CI_REGISTRY_IMAGE\" .\n    - docker push \"$CI_REGISTRY_IMAGE\""
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)\nand this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n## [0.4.0] - 2018-02-03\n\n### Added\n\n-   API basic HTTP auth\n\n### Changed\n\n-   Fixed orphaned neighbors check.\n-   Fixed API security bug.\n\n## [0.3.22] - 2018-01-29\n\n### Changed\n\n-   Fixed tests on some systems that were failing.\n-   Upgraded IOTA IRI JS Library to 0.4.7\n\n## [0.3.21] - 2018-01-24\n\n### Added\n\n-   Additional entry nodes\n-   Possible fix for #45 ECONNRESET error\n-   Interval-compression of the neighbors database\n-   IRI cleanup of neighbors. Possible fix for #50\n-   Additional Peer and PeerList tests. Fixes #43\n-   Guard tests\n-   Basic node tests\n-   Node network integration tests\n-   Basic node network simulation package\n-   Parts of the node simulation package for integration tests\n\n### Changed\n\n-   Upgraded minimal node version to 8.9.4\n-   Cleanup nelson on uncaught exception. Possible fix for #50\n-   Upgrades WebSockets to 4.0.0. Possible fix for #45\n-   Fixed docker to copy faster, ignoring unneeded files\n-   Made docker run the tests while building\n\n## [0.3.16] - 2018-01-09\n\n### Changed\n\n-   Fixed IRI TCP negotiation bug #5\n\n## [0.3.15] - 2018-01-09\n\n### Changed\n\n-   Fixed IRI TCP negotiation bug #4\n-   Removed binaries from the versioning\n-   Fixed terminal display\n\n## [0.3.12] - 2018-01-06\n\n### Changed\n\n-   Fixed IRI TCP negotiation bug #3\n\n## [0.3.11] - 2018-01-06\n\n### Changed\n\n-   Fixed IRI TCP negotiation bug #2\n\n## [0.3.9] - 2018-01-06\n\n### Changed\n\n-   Fixed IRI TCP negotiation bug\n\n## [0.3.8] - 2018-01-06\n\n### Added\n\n-   IRI protocol negotiation between nodes\n\n### Changed\n\n-   Fixed ECONNRESET bug.\n\n## [0.3.5] - 2018-01-02\n\n### Changed\n\n-   Fixes IPv6 check\n\n## [0.3.4] - 2018-01-02\n\n### Changed\n\n-   Fixed removed static neighbors on exit.\n-   Fixed possible neighbor leak in IRI.\n-   Fixes IPv6 URIs.\n-   Updated Dockerfile to make the build faster.\n\n## [0.3.1] - 2018-01-02\n\n### Added\n\n-   TCP switch for IRI\n\n### Changed\n\n-   Improved neighbor weighting algorithm. Fixed a few minor bugs.\n-   Smarter neighbor quality algorithm.\n-   Random peer dropping inversely-weighted by peer quality now.\n-   Improved incoming new/top peer rules.\n-   Restructured and cleaned up the README.\n-   Increased default minimal neighbors back to 5+6 (11) for stronger security.\n\n## [0.3.0] - 2017-12-27\n\n### Added\n\n-   IRI info to the API.\n-   Webhooks.\n-   Dynamic IP support.\n-   Node naming.\n-   Temporarily penalizing lazy/broken neighbors.\n\n### Changed\n\n-   Access to the whole peer list only from local requests.\n-   Fixes trust updating issues.\n\n## [0.2.5] - 2017-12-21\n\n### Added\n\n-   Request throttling guard.\n-   Made incoming/outgoing limits public.\n-   Warnings when setting too low incoming/outgoing limits.\n\n### Changed\n\n-   Updated iota.lib.js\n-   Fixes hard limits for nodes.\n-   Lowers the amount of minimum nodes to 9\n-   Limited the amount of recommended/shared nodes.\n-   Allowed cross-origin requests to API.\n\n## [0.2.4] - 2017-12-19\n\n### Added\n\n-   Readme info on pm2 manager and docker volume mounting.\n\n### Changes\n\n-   Makes Nelson ignore static neighbors completely, even if they run Nelson as well.\n\n## [0.2.3] - 2017-12-19\n\n### Added\n\n-   Ansible playbook for Nelson\n\n### Changed\n\n-   README docker ports for IRI\n-   Terminal: prevent box overlapping\n\n## [0.2.2] - 2017-12-18\n\n### Adds\n\n-   peer-stats to API\n-   Checking of NELSON_CONFIG env var for configuration path.\n\n## [0.2.1] - 2017-12-18\n\n### Changed\n\n-   Fixes getNeighbors when used in config.ini\n\n## [0.2.0] - 2017-12-18\n\n### Added\n\n-   Automatic entry nodes list downloading\n-   IRI healthchecks on startup without throwing an error.\n-   Actively remove peers, if the limit is trespassed at any point for any reason.\n-   Improved Dockerfile.\n\n## [0.1.11] - 2017-12-16\n\n### Changed\n\n-   Fixes IRI neighbors removal\n\n## [0.1.10] - 2017-12-16\n\n### Changed\n\n-   Replacing only incoming nodes with trusted nodes (possible limit breaker)\n\n## [0.1.9] - 2017-12-16\n\n### Changed\n\n-   Switched IRI to run in UDP mode due to TCP bugs in IRI. https://github.com/iotaledger/iri/issues/345\n\n## [0.1.8] - 2017-12-16\n\n### Added\n\n-   Delayed retry of unavailable peers.\n\n### Changed\n\n-   Default IRI API port: 14265\n-   Epoch time to 15 minutes\n-   Delayed neighbors remove from IRI (prevent orphans)\n\n### Removed\n\n-   Removed instant drops after handshake due to oft reconnects (moved into handshake)\n\n## [0.1.7] - 2017-12-15\n\n### Changed\n\n-   DNS resolve hostnames provided by IRI in health checks.\n\n## [0.1.6] - 2017-12-15\n\n### Changed\n\n-   Improved logs\n-   Fixed orphaned IRI neighbors\n\n## [0.1.5] - 2017-12-14\n\n### Changed\n\n-   Improved connection strategy to minimize reconnects.\n-   Improved incoming connection strategy to minimize dead nodes.\n\n## [0.1.4] - 2017-12-13\n\n### Added\n\n-   Fixed IRI health checks\n\n## [0.1.3] - 2017-12-13\n\n### Added\n\n-   Terminal GUI for Nelson\n-   IRI health checks\n\n### Changed\n\n-   Cleaned up logs (double-removals of peers)\n-   Minor bugfixes.\n\n## [0.1.1] - 2017-12-13\n\n### Added\n\n-   Option for setting nelson api listening hostname.\n\n### Changed\n\n-   Cleaned Docker README section.\n-   Cleaned up logs (double-removals of peers)\n\n## [0.1.0] - 2017-12-13\n\n### Added\n\n-   setting of IRI's hostname\n\n### Changed\n\n-   Dockerfile to use specific nelson version\n-   Readme about docker\n\n## [0.0.7] - 2017-12-13\n\n### Added\n\n-   Adds API versioning: drop connections from other major versions\n\n### Changed\n\n-   Fixes neighbors default port setting\n\n## [0.0.6] - 2017-12-12\n\n-   improve console log visualization\n-   added Dockerfile\n\n## [0.0.5] - 2017-12-12\n\n### Changed\n\n-   Dynamic openness in function with node's maturity.\n-   Sharing of opinion about neighbours.\n-   Implemented improved weighting from tri-tests.\n-   Decreased the average number of connected nodes to 8 (+/-4).\n\n## [0.0.4] - 2017-12-09\n\n### Added\n\n-   Contributing message\n\n### Changed\n\n-   forgotten dist and bin updates for 0.0.3\n\n## [0.0.3] - 2017-12-09\n\n### Added\n\n-   Command line params for incoming/outgoing slots count.\n\n### Changed\n\n-   How master nodes recycle peers (all) and treat outgoing connections.\n\n## [0.0.2] - 2017-12-08\n\n### Added\n\n-   Changelog\n-   Nelson API server for status updates incl README part\n\n### Changed\n\n-   Nelson API default port to 18600\n\n## [0.0.1] - 2017-12-06\n\nInitial version\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nWhen contributing to this repository, please first discuss the change you wish to make via issue,\nemail, or any other method with the owners of this repository before making a change. \n\nPlease note we have a code of conduct, please follow it in all your interactions with the project.\n\n## Pull Request Process\n\n1. Ensure any install or build dependencies are removed before the end of the layer when doing a \n   build.\n2. Update the README.md with details of changes to the interface, this includes new environment \n   variables, exposed ports, useful file locations and container parameters.\n3. Increase the version numbers in any examples files and the README.md to the new version that this\n   Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).\n4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you \n   do not have permission to do that, you may request the second reviewer to merge it for you.\n\n## Code of Conduct\n\n### Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n### Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\nadvances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n### Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n### Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n### Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at roman@deviota.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n### Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:8.9.4-alpine as builder\nCOPY . /usr/src/nelson\n\nWORKDIR /usr/src/nelson\nRUN npm install -g\n\nEXPOSE 16600\nEXPOSE 18600\n\nCMD [\"/usr/local/bin/nelson\"]\nENTRYPOINT [\"/usr/local/bin/nelson\"]\n"
  },
  {
    "path": "ENTRYNODES",
    "content": "mainnet.deviota.com/16600\nmainnet2.deviota.com/16600\niotairi.tt-tec.net/16600\nvoss-hosting.de/16600\n136.243.73.66/16600\niotanode.party/16600\nnelson.vanityfive.de/16600\ntangle.vanityfive.de/16600\ntanglenode.de/16600\nnelson.iota.fm/16000\nnode.io7a.com/16600\nus1.tangleno.de/16600\neu1.tangleno.de/16600\niota.bluemx.de/16600\nnelson.iotacore.de/16600\n"
  },
  {
    "path": "ISSUE_TEMPLATE.md",
    "content": "### Expected behaviour\n\n### Actual behaviour\n\n### Steps to reproduce\n\n### Basic Info\n* Operating System:\n* Node (npm) Version:\n* IRI Version:\n* Nelson version:\n\n### Nelson Info\n* Epoch:\n* Cycle:\n* Connected peers:\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright (c) 2017, Roman Semko - SemkoDev GbR\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Nelson\n\nNelson is a tool meant to be used with IOTA's IRI Node.\nIt automatically manages neighbors of your full node, negotiating connections,\nfinding new neighbors and protecting against bad actors.\n\n## Table of contents\n\n  * [Getting Started](#getting-started)\n    * [Prerequisites](#prerequisites)\n    * [Installing](#installing)\n    * [Upgrading](#upgrading)\n    * [Running as a service](#running-as-a-service)\n  * [Docker](#docker)\n  * [Building Locally](#building-locally)\n  * [Configuration](#configuration)\n    * [config.ini](#config.ini)\n    * [Command line options](#command-line-options)\n    * [Options description](#options-description)\n  * [Automated Scripts](#automated-scripts)\n    * [Amazon CloudFormation](#amazon-cloudformation)\n  * [Running Nelson](#running-nelson)\n    * [Initial nodes](#initial-nodes)\n    * [Epochs and Cycles](#epochs-and-cycles)\n    * [Monitor](#monitor)\n    * [API](#api)\n    * [Webhooks](#webhooks)\n  * [FAQ](#faq)\n  * [Contributing](#contributing)\n    * [Donations](#donations)\n    * [Running your own entry node](#running-your-own-entry-node)\n  * [Authors](#authors)\n  * [License](#license)\n\n## Getting Started\n\nThese instructions will get you a copy of the project up and running on your local machine.\n\n### Prerequisites\n\nIt is expected that you have already installed Java, downloaded the IRI jar file\nand know how to start it. The local IRI instance must have api enabled and allowing to add/remove neighbors.\n\nNelson is running on Node.js You will have to install **node (at least version LTS 8.9.4)** and *npm* (node package manager) on your system.\nAlternatively to npm you can (and should) use yarn package manager.\n\n#### Port Forwarding\n\nIf you are trying to run a Nelson node at home, you may need to open some ports (port forwarding) in your NAT Router:\n\n* **UDP 14600**\n* **TCP 15600**\n* **TCP 16600**\n\nPlease refer to your Router's manual on how to do that.\n\nFurthermore, please be aware that apart of firewall and port-forwarding in router, your Internet provider may also be an issue.\nSome providers (like Vodafone in Germany) do not have enough IPv4 addresses for homes and\nthus use something called \"**IPv4 over DS Lite**\". In those cases the **traffic will not come through** over the ports\nmentioned above. Unfortunately, there is no quick fix for this issue (maybe changing providers).\nThere is some hope with the upcoming PCP-protocol, this will not happen this year (2018) for most providers, though.\n\n#### WARNING FOR UBUNTU\n\nUbuntu 16.04 apt comes with an **outdated Node version (4.X)**. You need to install the latest version separately:\n\nhttps://nodejs.org/en/download/package-manager/\n\n### Installing\n\nGlobally install Nelson\n\n```\nnpm install -g nelson.cli\n```\n\nAnd run it\n\n```\nnelson --gui --getNeighbors\n```\n\nThe  ```--getNeighbors``` option is used to download an entry set of trusted Nelson peers for new Nelson instances.\nAs your Nelson stays online and gets to know its neighbors, it will rely less and less on the initial entry\npoints.\n\nThe  ```--gui``` option is used to provide a simple GUI interface in the console.\n\nBelow is the list of all possible options.\n\n### Upgrading\n\nTo upgrade your Nelson to version X.X.X, simply run:\n```\nnpm install -g nelson.cli@x.x.x\n```\n\n**Please check where npm installs your global packages**! It happens very often that the first installed binary\nis put into ```/usr/local/bin``` and the updated into ```/usr/bin```. Run ```nelson --version``` after the upgrade\nto make sure you are using the most recent one. Update your scripts and/or services to point to the right binary!\n\n### Running as a service\n\nYou can use the [node process manager](http://pm2.keymetrics.io/) to run Nelson as a service.\nJust do the following:\n```\n# Install the process manager:\nnpm install pm2 -g\n\n# Make pm2 start at startup:\npm2 startup\n\n# Start the Nelson as service\n# If you created a nelson config somewhere on your system, provide the path to the config:\npm2 start nelson -- --config /path/to/nelson-config.ini\n\n# Otherwise you can just do: pm2 start nelson\n\n# Save current processes runing with pm2 to startup on boot:\npm2 save\n\n# Get Nelson logs:\npm2 monit\n# or\npm2 log\n```\n\n## Docker\n\nProvided you have docker installed, Nelson can be started as follows:\n\n```\ndocker run <docker opts> romansemko/nelson.cli <nelson command line opts>\n```\n\nHence, running IRI with Nelson can be done with two simple commands:\n```\ndocker run -d --net host -p 14265:14265 --name iri iotaledger/iri\ndocker run -d --net host -p 18600:18600 --name nelson romansemko/nelson.cli -r localhost -i 14265 -u 14777 -t 15777 --neighbors \"mainnet.deviota.com/16600 mainnet2.deviota.com/16600 mainnet3.deviota.com/16600 iotairi.tt-tec.net/16600\"\n```\n\nThe options passed to Nelson's docker (```-r localhost -i 14265 -u 14600 -t 15600 --neighbors ...```) set IRI's\nhostname and ports (api, TCP, UDP) and the initial neighbors (You could also have used ```--getNeighbors```).\nPlease refer below for more info on options.\n\nTo keep Nelson's peer database outside of the container, so that you do not lose your collected neighbor's data,\nyou can mount a volume bound to a host's folder:\n\n```\ndocker run -d --net host -p 18600:18600 --name nelson -v /path/to/nelson/data/directory:/data romansemko/nelson.cli \n```\n\n## Building Locally\n\nIf you are a developer you may want to build the project locally and play around with the sources.\nOtherwise, ignore this section.\nMake sure you have [yarn](https://yarnpkg.com) package manager installed.\nCheckout the project:\n\n```\ngit clone https://github.com/SemkoDev/nelson.cli.git\ncd nelson.cli\n```\n\nInstall dependencies:\n\n```\nyarn install --pure-lockfile\n```\n\nRun tests and make binaries:\n\n```\nyarn make\n```\n\nTry to run Nelson:\n\n```\nnode ./dist/nelson.js --gui --getNeighbors\n```\n\n## Configuration\n\nYou are free to either use command line options or an ```.ini``` file to configure Nelson. If you use a config\nfile, it has precedence and all command line options are ignored.\n\n### config.ini\n\nTo use a configuration file, run Nelson with ```--config``` option:\n\n```\nnelson --config ./config.ini\n\n# Alternatively, set an environment variable:\nNELSON_CONFIG= ./config.ini nelson\n```\n\nYou can provide one or more of the following options in your ini file. Example:\n\n```\n[nelson]\nname = My Nelson Node\ncycleInterval = 60\nepochInterval = 300\napiPort = 18600\napiHostname = 127.0.0.1\nport = 16600\nIRIHostname = localhost\nIRIProtocol = any\nIRIPort = 14265\nTCPPort = 15600\nUDPPort = 14600\ndataPath = data/neighbors.db\n; maximal incoming connections. Please do not set below this limit:\nincomingMax = 5\n; maximal outgoing connections. Only set below this limit, if you have trusted, manual neighbors:\noutgoingMax = 4\nisMaster = false\nsilent = false\ngui = false\ngetNeighbors = https://raw.githubusercontent.com/SemkoDev/nelson.cli/master/ENTRYNODES\n; add as many initial Nelson neighbors, as you like\nneighbors[] = mainnet.deviota.com/16600\nneighbors[] = mainnet2.deviota.com/16600\nneighbors[] = mainnet3.deviota.com/16600\nneighbors[] = iotairi.tt-tec.net/16600\n\n; Protect API with basic auth\n[nelson.apiAuth]\nusername=user\npassword=pass\n```\n\n#### WARNING ON NEIGHBORS:\n\nThese are **NOT IRI neighbor** addresses, but the **Nelson** addresses. If you have used them erroneously\nas Nelson addresses in the past, chances are that Nelson will think these \"static\" neighbors are his and\nwill keep removing them from IRI.\n\nTo Fix this, just delete data/neighbors.db and start Nelson fresh with just ```--getNeighbors```\n\n### Command line options\n\nCommand line options are named the same as INI options.\nSome have additional short versions.\n\n### Options description\n\n| Option            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | Default           |\n| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |\n| --name            | Name your node. This identifier will appear in API/webhooks and for your neighbors                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |\n| --neighbors, -n   | space-separated list of entry Nelson neighbors                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |\n| --getNeighbors    | Downloads a list of entry Nelson neighbors. If no URL is provided, will use a default URL (https://raw.githubusercontent.com/SemkoDev/nelson.cli/master/ENTRYNODES). If this option is not set, no neighbors will be downloaded. This option can be used together with ````--neighbors``                                                                                                                                                                                                                                                                                                                                                                                     | false             |\n| --apiAuth         | Add basic HTTP auth to API. On the command line, please provide username and password in `user:pass` format. If you use config file, you will have to create a new `[nelson.apiAuth]` section with `username` and `password` See the example above.                                                                                                                                                                                                                                                                                                                                                                                                                          |\n| --apiPort, -a     | Nelson API port to request current node status data                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | 18600             |\n| --apiHostname, -o | Nelson API hostname to request current node status data. Default value will only listen to local connections                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | 127.0.0.1         |\n| --port, -p        | TCP port, on which to start your Nelson instance                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | 16600             |\n| --webhooks, -w    | List of URLS to regularly call back with the current node status data                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |\n| --webhookInterval | Interval in seconds between each webhook call                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | 30                |\n| --IRIHostname, -r | IRI API hostname of the running IRI node instance                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | localhost         |\n| --IRIPort, -i     | IRI API port of the running IRI node instance                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | 14265             |\n| --TCPPort, -t     | IRI TCP Port                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | 15600             |\n| --UDPPort, -u     | IRI UDP Port                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | 14600             |\n| --IRIProtocol     | Protocol to use for connecting neighbors. Possible values **'any'**, **'preferudp'**, **'prefertcp'**, **'udp'**, **'tcp'**. **WARNING**: Please only use with IRI v.1.4.1.6 and do not set to **udp** or **tcp** unless you are 100% sure that you cannot accept other protocol connections in no circumstances. Otherwise, setting **udp** will categorically deny connections from **tcp**-only hosts and vice-versa. **Durung the upgrade phase** setting to **tcp** will probably make your node unreachable as all of the older Nelson version nodes will be running **udp** only! Preferably set **preferudp** or **prefertcp**. \"**any**\" is always the best choice. | any               |\n| --dataPath, -d    | path to the file, that will be used as neighbor storage                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | data/neighbors.db |\n| --silent, -s      | Run the node without any output                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |\n| --gui, -g         | Run the node in console-gui mode                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |\n| --cycleInterval   | Interval between Nelson cycles                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | 60                |\n| --epochInterval   | Interval between Nelson epochs                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | 300               |\n| --isMaster        | Whether you are intending to run a master node                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |\n| --incomingMax     | How many incoming connections to accept. Please do not set below the default value!                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | 5                 |\n| --outgoingMax     | How many active/outgoing connections to establish. Please do not set below the default value, if you do not have any static/manual neighbors!                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | 4                 |\n| --lazyLimit       | After how many seconds a new Neighbors without new transactions should be dropped                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | 300               |\n| --lazyTimesLimit  | After how many consecutive connections from a consistently lazy neighbor, should it be penalized                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | 3                 |\n\n## Automated Scripts\n\n### Amazon CloudFormation\n\nThanks to [iotFab](https://github.com/iotFab) for creating the [cloudformation script](https://github.com/iotFab/iota-aws-full-node) to easily launch IRI+Nelson!\nIf You have an AWS account, you can launch a new full node in a matter of few clicks: \n\n[![alt text](https://s3.amazonaws.com/cloudformation-examples/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=IotaAwsFullNode&templateURL=https://s3-eu-west-1.amazonaws.com/nelson-iri/cloudformation.yml)\n\n1. Make sure \"Specify an Amazon S3 template URL\" is checked and continue.\n2. Click continue. You can leave all config with default values.\n3. If you want to be able to access your instance, you will need to provide a keypair. This is not required, though.\n4. Wait about 10 for the instance to launch.\n5. Done!\n\n## Running Nelson\n\n### Initial nodes\n\nThe neighbors you provide in the beginning are treated as trusted neighbors. This means that Nelson will be more inclined\nto accept contact requests from these neighbors and also to recommend them to other neighbors. They are also used as\ninitial contact for a young Nelson. They provide him with other neighbors' addresses.\n\n### Epochs and Cycles\n\nNelson grows. And with each new age (epoch), he treats his neighbors differently. A neighbor that he didn't like in the\npast, might become his best friend in the new epoch. The epoch option defines the interval in seconds between each epoch\nchange. Do not change it, unless you know, what you are doing.\n\nNelson checks upon its neighbors from time to time to make sure they are okay. Sometimes the neighbors die without saying\na word or maybe move somewhere else. Nelson wants to know, with whom he should keep in contact. Each cycle Nelson pings\nthe neighbors, to make sure they are okay. You can control the cycle interval with the ```cycleInterval``` option.\n\n### Monitor\nThere is a simple [Nelson server/monitor](https://github.com/SemkoDev/nelson.gui) available at: https://github.com/SemkoDev/nelson.gui\nThis is work in progress, so please bear with the simplicity.\n\nYou might need to run your nelson.cli with ```--apiHostname 0.0.0.0``` so that the monitor web-app has \naccess to the Nelson API server.\n\n### API\n\nNelson comes with a simple API to get its current status:\n\n```\n# Replace the port, if you changed it when starting Nelson:\ncurl http://localhost:18600\n\n# Answer:\n{\n    \"ready\": true,\n    \"totalPeers\": 200,\n    \"connectedPeers\": [\n        {\n            \"hostname\": \"xxxxxxxxxxxxxxx\",\n            \"ip\": \"xxxxxxxxxxxxxxxx\",\n            \"port\": 16600,\n            \"TCPPort\": 15600,\n            \"UDPPort\": 14600,\n            \"seen\": 1,\n            \"connected\": 50,\n            \"tried\": 0,\n            \"weight\": 0.75,\n            \"dateTried\": \"2017-12-18T07:58:10.614Z\",\n            \"dateLastConnected\": \"2017-12-18T07:58:10.705Z\",\n            \"dateCreated\": \"2017-12-17T00:07:16.787Z\",\n            \"isTrusted\": false,\n            \"_id\": \"pOsnVKeGtWufM6AI\",\n            \"nelsonID\": \"544a0355\"\n        },\n        ...\n    ],\n    \"config\": {\n        \"cycleInterval\": 60,\n        \"epochInterval\": 900,\n        \"beatInterval\": 10,\n        \"dataPath\": \"/data/neighbors.db\",\n        \"port\": 16600,\n        \"apiPort\": 18600,\n        \"IRIPort\": 14265,\n        \"TCPPort\": 15777,\n        \"UDPPort\": 14777,\n        \"isMaster\": false,\n        \"temporary\": false\n    },\n    \"heart\": {\n        \"lastCycle\": \"2017-12-18T08:10:07.806Z\",\n        \"lastEpoch\": \"2017-12-18T08:01:02.967Z\",\n        \"personality\": {\n            \"id\": \"d856113128efbb33d313f7a5bd2c6befa40923544a5ae478613e4ac4c0cd0314341f1b4c6fcc30fd5cfe08a1db709a2f\",\n            \"publicId\": \"d8561131\",\n            \"feature\": \"e\"\n        },\n        \"currentCycle\": 1944,\n        \"currentEpoch\": 130,\n        \"startDate\": \"2017-12-16T23:40:04.615Z\"\n    }\n```\n\nYou can also get the full list of known peers:\n\n```\ncurl http://localhost:18600/peers\n```\n\nOr just the short stats about your known peers:\n\n```\ncurl http://localhost:18600/peer-stats\n\n#Output:\n{\n    \"newNodes\": {\n        \"hourAgo\": 43,\n        \"fourAgo\": 275,\n        \"twelveAgo\": 733,\n        \"dayAgo\": 1825,\n        \"weekAgo\": 2466\n    },\n    \"activeNodes\": {\n        \"hourAgo\": 133,\n        \"fourAgo\": 463,\n        \"twelveAgo\": 950,\n        \"dayAgo\": 2133,\n        \"weekAgo\": 2257\n    }\n}\n```\n\nif you use `apiAuth` option to protect your API, you will need to provide the authentication details\nin your requests:\n\n```\ncurl -u username:password http://localhost:18600\n```\n\n### Webhooks\n\nYou can provide Nelson a list of webhook URLs that have to be regularly called back with all the node stats data.\nIt basically provides the same data as calling ```curl http://localhost:18600/``` API.\n\nAll webhook requests are POST requests. To add a webhook to nelson, start it with ```--webhooks``` option:\n\n```\nnelson --webhooks \"http://webhook.one/ http://webhook.two/\"\n```\n\n## FAQ\n\n### Help! Nelson isn't connecting to neighbors!\n\nDepending on Nelson's age/epoch he might or might not like a certain neighbor. That's okay. Just wait for the neighbor\nto mature and he might accept you into his circle.\n\nThis is more acute for new nodes without any neighbors at all.\nYou might need to wait for quite some time to be accepted into the network.\n\nThe same happens to your own Nelson instance. It might deny contact from new neighbors or those he doesn't know well.\nThe less trusted and less known a neighbor is, the less likely your Nelson will contact him. This is a security measure\nto slowly structure the network and give more weight to old, trusted neighborhood. You can read more about it in the\nNelson's release article: https://semkodev.com/nelson-in-a-nutshell/\n\n### Nelson is still not connecting!\n\nMake sure that Nelson's port (default: 16600) is not firewalled.\n\n### Nelson connects to the neighbors, but I am not getting any transactions\n\nMake sure that you provided the correct TCP/UDP IRI ports to Nelson. If your ports differ from the defaults\n(TCP: 15600 and UDP: 14600) you have to provide them!\n\n### Nelson constantly connects/disconnects\n\nNelson generates a lot of log output. Each handshake try and fail generates at least 3 lines of logs:\n\n- Connecting\n- Closing connection\n- Removing neighbor from IRI (although non has been added, yet).\n\nThis is Normal.\n\n### I have too many neighbors\n\nNelson adds up to 10/11 additional neighbors. If you have a lot of \"manual\" neighbors, this might be too much.\n\n### I am getting an error:\n\n```\n usr/bin/env: »node“ Unknown command...\n```\n\nMake sure you have node v.8.9.4 or higher installed on your machine.\n\n### I am getting an error:\n\n```\nmodule.exports = (externalConfig = {}) => {\n                                ^\n\nSyntaxError: Unexpected token =\n   at exports.runInThisContext (vm.js:53:16)\n   at Module._compile (module.js:374:25)\n   at Object.Module._extensions..js (module.js:417:10)\n   at Module.load (module.js:344:32)\n   at Function.Module._load (module.js:301:12)\n   at Module.require (module.js:354:17)\n   at require (internal/module.js:12:17)\n   at Object.<anonymous> (/usr/local/lib/node_modules/nelson.cli/node_modules/external-ip/index.js:2:18)\n   at Module._compile (module.js:410:26)\n   at Object.Module._extensions..js (module.js:417:10)\n```\nYour node version is outdated. Make sure you have node v.6.9.1 or higher installed on your machine.\n\n### I upgraded nelson, but it's still the old version!\n\nPlease refer to [upgrading](#upgrading) for a possible reason.\n\n## Contributing\n\n### Running your own entry node\n\nAs the network grows, we will need more entry nodes. These \"master\" nodes serve as gates to the\nnetwork for new nodes. They accept slightly more connections and do not actively connect to others.\nThe entry nodes only share info about the nodes that have contacted them sometime in the past.\n\nYou can run a master node by adding these options to Nelson:\n\n```\n--isMaster --epochInterval 180 --incomingMax 9\n```\nThe first value tells Nelson to run in \"master\" mode. The second decreases the epoch time so that\nthe connected nodes are rotated faster, giving space to new nodes. The third increases the amount\nof accepted connections (since master nodes do not have active connections, the outgoingMax for masters does not do anything).\n\nYou can contact the maintainer of this repo (http://www.twitter.com/RomanSemko) to get your node\nincluded here. An initiative for donations to entry nodes is under way.\n\n## Authors\n\n* **Roman Semko** - *SemkoDev* - (https://github.com/romansemko)\n* **Vitaly Semko** - *SemkoDev* - (https://github.com/witwit)\n\n## License\n\nThis project is licensed under the ICS License - see the [LICENSE.md](LICENSE.md) file for details\n\n"
  },
  {
    "path": "config.ini.example",
    "content": "[nelson]\ncycleInterval = 60\nepochInterval = 300\napiPort = 18600\nport = 16600\nIRIPort = 14265\nTCPPort = 15600\nUDPPort = 14600\ndataPath = data/neighbors.db\nisMaster = false\nsilent = false\n; use automatic service to download latest initial nodes\ngetNeighbors = https://raw.githubusercontent.com/SemkoDev/nelson.cli/master/ENTRYNODES\n; or/and add as many initial nelson neighbors, as you like\nneighbors[] = mainnet.deviota.com/14600\nneighbors[] = mainnet2.deviota.com/14600\nneighbors[] = mainnet3.deviota.com/14600\n"
  },
  {
    "path": "contrib/ansible-playbook/.gitignore",
    "content": "site.retry\n.*.swp\n"
  },
  {
    "path": "contrib/ansible-playbook/README.md",
    "content": "# IOTA Nelson (IRI) Fullnode Ansible Playbook\n\nThis playbook will install IRI and Nelson As Docker containers.\n\n\n## Requirements\n\n\n### Operating System\nThis playbook has been tested on:\n\n* Ubuntu 16.04 and 17.04\n* CentOS 7.4\n\n### Software Dependencies\n\n**Note** Docker CE will be installed by the playbook, it is not strictly required to install it before running the playbook.\n\n* Docker CE\n\nFor Ubuntu: https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/\n\nFor CentOS: https://docs.docker.com/engine/installation/linux/docker-ce/centos/ \n\n* Ansible >= 2.4\n\nTo install Ansible:\n\n**Ubuntu**:\n\n```sh\napt-get upgrade -y && apt-get clean && apt-get update -y && apt-get install software-properties-common -y && apt-add-repository ppa:ansible/ansible -y && apt-get update -y && apt-get install ansible -y\n```\n\n**CentOS**:\n\n```sh\nyum install ansible -y\n```\n\n#### Consideration\nConsider to run the playbook within a screen session. Should the SSH connection drop, the playbook's session will remain active.\n\nEnsure `screen` is installed:\n**Ubuntu**:\n```sh\napt-get install screen -y\n```\n\n**CentOS**:\n```sh\nyum install screen -y\n```\n\nThen use `screen -S nelson` to create a session and run the next commands within.\n\nTo detach from the session, press `CTRL-A` and `d`.\n\nTo reattach to a session `screen -r nelson` or `screen -D -r nelson` if the screen is still attached.\n\nUse `exit` or `CTRL-D` within the session to end the session.\n\n## Configuration\n\nIf you want to configure values before running the playbook you will find the variables in the files under:\n```sh\ngroup_vars/all/*.yml\n```\n\n## Installation\n\nRun:\n```sh\nansible-playbook -i inventory -v site.yml\n```\n\nSpecifc roles and or tasks can be run individually or skipped using `--tags=tag_name_a,tag_name_b` or `--skip-tags=tag_name`.\n\n\n## Controls\n\nTo start, stop or view status of either `nelson` or `iri` run:\n\n```sh\nsystemctl status iri\n```\n\nReplace the service name or command as required.\n\n## Logs\n\nTo view the logs of either `nelson` or `iri` run:\n\n```sh\njournalctl -u iri\n```\n\nUse `shift-g` to scroll to the bottom.\n\nAlterntively, to avoid using the pager:\n```sh\njournalctl -u nelson --no-pager -n50\n```\n\nThis command will display the last 50 lines of the log.\n\nYou can use `-f` to follow the tail of the log.\n\n## File Locations\n\n* Nelson's configuration is at `/etc/nelson/config.ini`\n* IRI config is at `/etc/iri/iri.ini`\n* Nelson's data directory is at `/var/lib/nelson/`\n* IRI's database is at `/var/lib/iri/`\n\n"
  },
  {
    "path": "contrib/ansible-playbook/group_vars/all/common.yml",
    "content": "# source: https://github.com/geerlingguy/ansible-role-docker/blob/dd0c6e0f8ee3aa5a9638d5318593fc80f2eaacfb/defaults/main.yml\n\n# Edition can be'ce' (Community Edition) or 'ee' (Enterprise Edition).\ndocker_edition: 'ce'\ndocker_package: \"docker-{{ docker_edition }}\"\ndocker_package_state: present\n\n# Used only for Debian/Ubuntu. Switch 'stable' to 'edge' if needed.\ndocker_apt_release_channel: stable\ndocker_apt_repository: \"deb https://download.docker.com/linux/{{ ansible_distribution|lower }} {{ ansible_distribution_release }} {{ docker_apt_release_channel }}\"\n\n# Used only for RedHat/CentOS.\ndocker_yum_repo_url: https://download.docker.com/linux/centos/docker-{{ docker_edition }}.repo\ndocker_yum_repo_enable_edge: 0\ndocker_yum_repo_enable_test: 0\n\n# Globals\nssh_port: 22\n"
  },
  {
    "path": "contrib/ansible-playbook/group_vars/all/iri.yml",
    "content": "# Unprivileged user to run iri with\niri_username: iri\n\n# Base directory where iri is installed and runs from\niri_basedir: /var/lib/iri\n\n# IRI configuration dir\niri_configdir: /etc/iri\n\n# IRI docker image\niri_image: nuriel77/iri-image\n\n# IRI Docker image tag\niri_tag: latest\n\n# The TCP port on which IRI listens for API calls and allows for light wallets to connect to\niri_api_port: 14265\n\n# The UDP neighbor peering port\niri_udp_port: 14600\n\n# The TCP neighbor peering port\niri_tcp_port: 14600\n\n# Limit IRI memory usage\niri_java_mem: 4096m\n\n# Initial memory usage\niri_init_java_mem: 768m\n\n# Automatically configure memory limits\n# Overrides above `iri_java_mem` value\nmemory_autoset: true\n\n# Let the iri_api_port bind to all interfaces (0.0.0.0).\n# `true` is necessary in order to allow external wallets/APIs to connect without tunneling.\n# If set to `false` it will only bind to localhost (127.0.0.1)\n# If setting to `true`, make sure you use the `iri_remote_limit_api` to limit what users can do.\napi_port_remote: false\n"
  },
  {
    "path": "contrib/ansible-playbook/group_vars/all/nelson.yml",
    "content": "# Nelson docker image tag to run\nnelson_tag: latest\n\n# Nelson image name\nnelson_image: romansemko/nelson\n\n# User name under which to run nelson\nnelson_username: nelson\n\n# Nelson configuration directory\nnelson_configdir: /etc/nelson\n\n# Nelson data dir\nnelson_datadir: /var/lib/nelson\n\n# IRI host on which to bind to IRI\nnelson_iri_host: 127.0.0.1\n\n# Nelson bind API to this address\nnelson_bind_address: 127.0.0.1\n\n# Nelson API port\nnelson_api_port: 18600\n\n# Nelson communication TCP port\nnelson_tcp_port: 16600\n"
  },
  {
    "path": "contrib/ansible-playbook/inventory",
    "content": "[fullnode]\n# Here the host on which to run the playbook on.\n# If using a remote host use either IP or a resolvable name.\n# Also, remove the ansible_connection=local if a remote host.\nlocalhost ansible_connection=local\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/common/handlers/main.yml",
    "content": "- name: reload systemd\n  sudo: yes\n  command: systemctl daemon-reload\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/common/tasks/firewalld.yml",
    "content": "- name: ensure firewalld started and enabled\n  systemd:\n    name: firewalld\n    state: started\n    enabled: yes\n\n- name: ensure selinux enabled\n  selinux:\n    policy: targeted\n    state: enforcing\n  register: selinux_enabled\n\n- name: check selinux not disabled\n  shell: \"getenforce\"\n  changed_when: false\n  register: getenforce\n\n- name: exit and notify reboot required if selinux got enabled\n  block:\n    - name: exit and notify reboot required to get selinux enabled\n      debug:\n        msg: \"** NOTE *** Selinux was disabled on this host. It has now been enabled. Please reboot this host `shutdown -r now` and re-run this playbook.\"\n    - meta: end_play\n  when: selinux_enabled is defined and selinux_enabled.changed\n\n- name: exit and notify selinux not enabled\n  block:\n    - name: exit and notify selinux not enabled\n      debug:\n        msg: >\n             ** NOTE ** The system might require a reboot to get selinux enabled.\n                        Check /etc/sysconfig/selinux if selinux is `enforcing`.\n                        If it is, the host needs to be rebooted `shutdown -r now`.\n                        Refusing to continue.\n    - meta: end_play\n  when: \"getenforce is defined and 'stdout' in getenforce and 'Disabled' in getenforce.stdout\"\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/common/tasks/install.yml",
    "content": "- name: Install Docker\n  package:\n    name: \"{{ docker_package }}\"\n    state: \"{{ docker_package_state }}\"\n\n- name: Ensure Docker is started and enabled\n  service:\n    name: docker\n    state: started\n    enabled: yes\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/common/tasks/main.yml",
    "content": "- import_tasks: role.yml\n  tags:\n    - common_role\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/common/tasks/role.yml",
    "content": "- import_tasks: setup_apt.yml\n  tags:\n    - common_setup_apt\n  when: ansible_distribution == 'Ubuntu'\n\n- import_tasks: setup_yum.yml\n  tags:\n    - common_setup_yum\n  when: ansible_distribution == 'CentOS'\n\n- import_tasks: install.yml\n  tags:\n    - common_install\n\n- import_tasks: setup_pip.yml\n  tags:\n    - common_setup_pip\n\n- import_tasks: firewalld.yml\n  tags:\n    - common_firewalld\n  when: ansible_distribution == 'CentOS'\n\n- import_tasks: ufw.yml\n  tags:\n    - common_ufw\n  when: ansible_distribution == 'Ubuntu'\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/common/tasks/setup_apt.yml",
    "content": "# Source: https://github.com/geerlingguy/ansible-role-docker/blob/master/tasks/setup-Debian.yml\n\n- name: Ensure depdencies are installed\n  apt:\n    name: \"{{ item }}\"\n    state: present\n  with_items:\n    - apt-transport-https\n    - ca-certificates\n    - jq\n    - ufw\n    - wget\n    - lsof\n    - curl\n    - pv\n    - python-pip\n\n- name: Add Docker apt key\n  apt_key:\n    url: https://download.docker.com/linux/ubuntu/gpg\n    id: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88\n    state: present\n  register: add_repository_key\n  ignore_errors: true\n\n- name: Ensure curl is present (on older systems without SNI).\n  package: name=curl state=present\n  when: add_repository_key|failed\n\n- name: Add Docker apt key (alternative for older systems without SNI).\n  shell: \"curl -sSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -\"\n  args:\n    warn: no\n  when: add_repository_key|failed\n\n- name: Add Docker repository.\n  apt_repository:\n    repo: \"{{ docker_apt_repository }}\"\n    state: present\n  update_cache: yes\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/common/tasks/setup_pip.yml",
    "content": "- name: install python deps via pip\n  pip:\n    name: docker-py \n"
  },
  {
    "path": "contrib/ansible-playbook/roles/common/tasks/setup_yum.yml",
    "content": "# Required\n- name: Install epel-release\n  yum: state=latest name=epel-release\n\n- name: Install some packages\n  yum: state=latest name={{ item }}\n  with_items:\n    - policycoreutils-python\n    - firewalld\n    - curl\n    - wget\n    - screen\n    - lsof\n    - jq\n    - pv\n    - python-pip\n\n- name: Add Docker GPG key\n  rpm_key:\n    key: https://download.docker.com/linux/centos/gpg\n    state: present\n\n- name: Add Docker repository.\n  get_url:\n    url: \"{{ docker_yum_repo_url }}\"\n    dest: '/etc/yum.repos.d/docker-{{ docker_edition }}.repo'\n    owner: root\n    group: root\n    mode: 0644\n\n- name: Configure Docker Edge repo.\n  ini_file:\n    dest: '/etc/yum.repos.d/docker-{{ docker_edition }}.repo'\n    section: 'docker-{{ docker_edition }}-edge'\n    option: enabled\n    value: '{{ docker_yum_repo_enable_edge }}'\n\n- name: Configure Docker Test repo.\n  ini_file:\n    dest: '/etc/yum.repos.d/docker-{{ docker_edition }}.repo'\n    section: 'docker-{{ docker_edition }}-test'\n    option: enabled\n    value: '{{ docker_yum_repo_enable_test }}'\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/common/tasks/ufw.yml",
    "content": "- name: allow ssh port firewall\n  ufw:\n    rule: allow\n    direction: in\n    proto: tcp\n    port: \"{{ ssh_port }}\"\n\n- name: ufw default outgoing policy allowed\n  ufw:\n    direction: outgoing\n    policy: allow\n\n- name: ensure ufw started and default incoming policy denied\n  ufw:\n    state: enabled\n    direction: incoming\n    policy: deny\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/iri/files/iri.service",
    "content": "[Unit]\nDescription=IRI Fullnode Docker Container\nRequires=docker.service\nAfter=docker.service\n\n[Service]\nRestart=on-failure\nRestartSec=10\nExecStart=/usr/bin/docker start -a %p\nExecStop=-/usr/bin/docker stop -t 2 %p\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/iri/files/nbctl",
    "content": "#!/usr/bin/env python\nimport argparse\nimport urllib2\nimport json\nimport sys\n\n\n\"\"\"Script to add or remove neighbors\n   from IRI API.\n   source: https://github.com/nuriel77/iri-playbook\n\"\"\"\n\ndef parse_args():\n\n    parser = argparse.ArgumentParser(\n        description='Add or remove full node neighbors.',\n        epilog='Example: nbctl -a -n'\n               ' udp://1.2.3.4:12345 -n tcp://4.3.2.1:4321')\n\n    parser.add_argument('--neighbors', '-n',\n                        action='append',\n                        required=True,\n                        help='Neighbors to process. Can be specified'\n                             ' multiple times.')\n\n    parser.add_argument('--remove', '-r', action='store_true',\n                        help='Removes neighbors,')\n\n    parser.add_argument('--add', '-a', action='store_true',\n                        help='Add neighbors')\n\n    parser.add_argument('--host', '-i',\n                        default='http://localhost:14265',\n                        help='IRI API endpoint. Default: %(default)s')\n\n    parser.add_argument('--api-version', '-x',\n                        default='1.4',\n                        help='IRI API Version. Default: %(default)s')\n\n    return parser.parse_args()\n\n\ndef run():\n    try:\n        args = parse_args()\n    except Exception as e:\n        sys.stderr.write(\"Error parsing arguments: %s\\n\" % e)\n        sys.exit(1)\n\n    if args.add and args.remove:\n        sys.stderr.write(\"You can either select `--add` or `--remove`\"\n                         \", not both.\\n\")\n        sys.exit(1)\n    elif not args.add and not args.remove:\n        sys.stderr.write(\"You must select either `--add` or `--remove`\\n\")\n        sys.exit(1)\n\n    command = 'addNeighbors' if args.add else 'removeNeighbors'\n\n    headers = {\n        'content-type': 'application/json',\n        'X-IOTA-API-Version': args.api_version\n    }\n\n    to_send = json.dumps({\n        'command': command,\n        'uris': args.neighbors\n    })\n\n    request = urllib2.Request(url=args.host,\n                              data=to_send,\n                              headers=headers)\n\n    return_data = urllib2.urlopen(request).read()\n    json_data = json.loads(return_data)\n    print(json_data)\n\n\nif __name__ == \"__main__\":\n    run()\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/iri/files/ps_mem",
    "content": "#!/usr/bin/env python\n\n# Try to determine how much RAM is currently being used per program.\n# Note per _program_, not per process. So for example this script\n# will report RAM used by all httpd process together. In detail it reports:\n# sum(private RAM for program processes) + sum(Shared RAM for program processes)\n# The shared RAM is problematic to calculate, and this script automatically\n# selects the most accurate method available for your kernel.\n\n# Licence: LGPLv2\n# Author:  P@draigBrady.com\n# Source:  http://www.pixelbeat.org/scripts/ps_mem.py\n\n# V1.0      06 Jul 2005     Initial release\n# V1.1      11 Aug 2006     root permission required for accuracy\n# V1.2      08 Nov 2006     Add total to output\n#                           Use KiB,MiB,... for units rather than K,M,...\n# V1.3      22 Nov 2006     Ignore shared col from /proc/$pid/statm for\n#                           2.6 kernels up to and including 2.6.9.\n#                           There it represented the total file backed extent\n# V1.4      23 Nov 2006     Remove total from output as it's meaningless\n#                           (the shared values overlap with other programs).\n#                           Display the shared column. This extra info is\n#                           useful, especially as it overlaps between programs.\n# V1.5      26 Mar 2007     Remove redundant recursion from human()\n# V1.6      05 Jun 2007     Also report number of processes with a given name.\n#                           Patch from riccardo.murri@gmail.com\n# V1.7      20 Sep 2007     Use PSS from /proc/$pid/smaps if available, which\n#                           fixes some over-estimation and allows totalling.\n#                           Enumerate the PIDs directly rather than using ps,\n#                           which fixes the possible race between reading\n#                           RSS with ps, and shared memory with this program.\n#                           Also we can show non truncated command names.\n# V1.8      28 Sep 2007     More accurate matching for stats in /proc/$pid/smaps\n#                           as otherwise could match libraries causing a crash.\n#                           Patch from patrice.bouchand.fedora@gmail.com\n# V1.9      20 Feb 2008     Fix invalid values reported when PSS is available.\n#                           Reported by Andrey Borzenkov <arvidjaar@mail.ru>\n# V3.9      07 Mar 2017\n#   http://github.com/pixelb/scripts/commits/master/scripts/ps_mem.py\n\n# Notes:\n#\n# All interpreted programs where the interpreter is started\n# by the shell or with env, will be merged to the interpreter\n# (as that's what's given to exec). For e.g. all python programs\n# starting with \"#!/usr/bin/env python\" will be grouped under python.\n# You can change this by using the full command line but that will\n# have the undesirable affect of splitting up programs started with\n# differing parameters (for e.g. mingetty tty[1-6]).\n#\n# For 2.6 kernels up to and including 2.6.13 and later 2.4 redhat kernels\n# (rmap vm without smaps) it can not be accurately determined how many pages\n# are shared between processes in general or within a program in our case:\n# http://lkml.org/lkml/2005/7/6/250\n# A warning is printed if overestimation is possible.\n# In addition for 2.6 kernels up to 2.6.9 inclusive, the shared\n# value in /proc/$pid/statm is the total file-backed extent of a process.\n# We ignore that, introducing more overestimation, again printing a warning.\n# Since kernel 2.6.23-rc8-mm1 PSS is available in smaps, which allows\n# us to calculate a more accurate value for the total RAM used by programs.\n#\n# Programs that use CLONE_VM without CLONE_THREAD are discounted by assuming\n# they're the only programs that have the same /proc/$PID/smaps file for\n# each instance.  This will fail if there are multiple real instances of a\n# program that then use CLONE_VM without CLONE_THREAD, or if a clone changes\n# its memory map while we're checksumming each /proc/$PID/smaps.\n#\n# I don't take account of memory allocated for a program\n# by other programs. For e.g. memory used in the X server for\n# a program could be determined, but is not.\n#\n# FreeBSD is supported if linprocfs is mounted at /compat/linux/proc/\n# FreeBSD 8.0 supports up to a level of Linux 2.6.16\n\nimport getopt\nimport time\nimport errno\nimport os\nimport sys\n\n# The following exits cleanly on Ctrl-C or EPIPE\n# while treating other exceptions as before.\ndef std_exceptions(etype, value, tb):\n    sys.excepthook = sys.__excepthook__\n    if issubclass(etype, KeyboardInterrupt):\n        pass\n    elif issubclass(etype, IOError) and value.errno == errno.EPIPE:\n        pass\n    else:\n        sys.__excepthook__(etype, value, tb)\nsys.excepthook = std_exceptions\n\n#\n#   Define some global variables\n#\n\nPAGESIZE = os.sysconf(\"SC_PAGE_SIZE\") / 1024 #KiB\nour_pid = os.getpid()\n\nhave_pss = 0\nhave_swap_pss = 0\n\nclass Proc:\n    def __init__(self):\n        uname = os.uname()\n        if uname[0] == \"FreeBSD\":\n            self.proc = '/compat/linux/proc'\n        else:\n            self.proc = '/proc'\n\n    def path(self, *args):\n        return os.path.join(self.proc, *(str(a) for a in args))\n\n    def open(self, *args):\n        try:\n            if sys.version_info < (3,):\n                return open(self.path(*args))\n            else:\n                return open(self.path(*args), errors='ignore')\n        except (IOError, OSError):\n            val = sys.exc_info()[1]\n            if (val.errno == errno.ENOENT or # kernel thread or process gone\n                val.errno == errno.EPERM or\n                val.errno == errno.EACCES):\n                raise LookupError\n            raise\n\nproc = Proc()\n\n\n#\n#   Functions\n#\n\ndef parse_options():\n    try:\n        long_options = [\n            'split-args',\n            'help',\n            'total',\n            'discriminate-by-pid',\n            'swap'\n        ]\n        opts, args = getopt.getopt(sys.argv[1:], \"shtdSp:w:\", long_options)\n    except getopt.GetoptError:\n        sys.stderr.write(help())\n        sys.exit(3)\n\n    if len(args):\n        sys.stderr.write(\"Extraneous arguments: %s\\n\" % args)\n        sys.exit(3)\n\n    # ps_mem.py options\n    split_args = False\n    pids_to_show = None\n    discriminate_by_pid = False\n    show_swap = False\n    watch = None\n    only_total = False\n\n    for o, a in opts:\n        if o in ('-s', '--split-args'):\n            split_args = True\n        if o in ('-t', '--total'):\n            only_total = True\n        if o in ('-d', '--discriminate-by-pid'):\n            discriminate_by_pid = True\n        if o in ('-S', '--swap'):\n            show_swap = True\n        if o in ('-h', '--help'):\n            sys.stdout.write(help())\n            sys.exit(0)\n        if o in ('-p',):\n            try:\n                pids_to_show = [int(x) for x in a.split(',')]\n            except:\n                sys.stderr.write(help())\n                sys.exit(3)\n        if o in ('-w',):\n            try:\n                watch = int(a)\n            except:\n                sys.stderr.write(help())\n                sys.exit(3)\n\n    return (\n        split_args,\n        pids_to_show,\n        watch,\n        only_total,\n        discriminate_by_pid,\n        show_swap\n    )\n\n\ndef help():\n    help_msg = 'Usage: ps_mem [OPTION]...\\n' \\\n        'Show program core memory usage\\n' \\\n        '\\n' \\\n        '  -h, -help                   Show this help\\n' \\\n        '  -p <pid>[,pid2,...pidN]     Only show memory usage PIDs in the '\\\n        'specified list\\n' \\\n        '  -s, --split-args            Show and separate by, all command line'\\\n        ' arguments\\n' \\\n        '  -t, --total                 Show only the total value\\n' \\\n        '  -d, --discriminate-by-pid   Show by process rather than by program\\n' \\\n        '  -S, --swap                  Show swap information\\n' \\\n        '  -w <N>                      Measure and show process memory every'\\\n        ' N seconds\\n'\n\n    return help_msg\n\n\n# (major,minor,release)\ndef kernel_ver():\n    kv = proc.open('sys/kernel/osrelease').readline().split(\".\")[:3]\n    last = len(kv)\n    if last == 2:\n        kv.append('0')\n    last -= 1\n    while last > 0:\n        for char in \"-_\":\n            kv[last] = kv[last].split(char)[0]\n        try:\n            int(kv[last])\n        except:\n            kv[last] = 0\n        last -= 1\n    return (int(kv[0]), int(kv[1]), int(kv[2]))\n\n\n#return Private,Shared\n#Note shared is always a subset of rss (trs is not always)\ndef getMemStats(pid):\n    global have_pss\n    global have_swap_pss\n    mem_id = pid #unique\n    Private_lines = []\n    Shared_lines = []\n    Pss_lines = []\n    Rss = (int(proc.open(pid, 'statm').readline().split()[1])\n           * PAGESIZE)\n    Swap_lines = []\n    Swap_pss_lines = []\n\n    Swap = 0\n    Swap_pss = 0\n\n    if os.path.exists(proc.path(pid, 'smaps')):  # stat\n        lines = proc.open(pid, 'smaps').readlines()  # open\n        # Note we checksum smaps as maps is usually but\n        # not always different for separate processes.\n        mem_id = hash(''.join(lines))\n        for line in lines:\n            if line.startswith(\"Shared\"):\n                Shared_lines.append(line)\n            elif line.startswith(\"Private\"):\n                Private_lines.append(line)\n            elif line.startswith(\"Pss\"):\n                have_pss = 1\n                Pss_lines.append(line)\n            elif line.startswith(\"Swap:\"):\n                Swap_lines.append(line)\n            elif line.startswith(\"SwapPss:\"):\n                have_swap_pss = 1\n                Swap_pss_lines.append(line)\n        Shared = sum([int(line.split()[1]) for line in Shared_lines])\n        Private = sum([int(line.split()[1]) for line in Private_lines])\n        #Note Shared + Private = Rss above\n        #The Rss in smaps includes video card mem etc.\n        if have_pss:\n            pss_adjust = 0.5 # add 0.5KiB as this avg error due to truncation\n            Pss = sum([float(line.split()[1])+pss_adjust for line in Pss_lines])\n            Shared = Pss - Private\n        # Note that Swap = Private swap + Shared swap.\n        Swap = sum([int(line.split()[1]) for line in Swap_lines])\n        if have_swap_pss:\n            # The kernel supports SwapPss, that shows proportional swap share.\n            # Note that Swap - SwapPss is not Private Swap.\n            Swap_pss = sum([int(line.split()[1]) for line in Swap_pss_lines])\n    elif (2,6,1) <= kernel_ver() <= (2,6,9):\n        Shared = 0 #lots of overestimation, but what can we do?\n        Private = Rss\n    else:\n        Shared = int(proc.open(pid, 'statm').readline().split()[2])\n        Shared *= PAGESIZE\n        Private = Rss - Shared\n    return (Private, Shared, mem_id, Swap, Swap_pss)\n\n\ndef getCmdName(pid, split_args, discriminate_by_pid):\n    cmdline = proc.open(pid, 'cmdline').read().split(\"\\0\")\n    if cmdline[-1] == '' and len(cmdline) > 1:\n        cmdline = cmdline[:-1]\n\n    path = proc.path(pid, 'exe')\n    try:\n        path = os.readlink(path)\n        # Some symlink targets were seen to contain NULs on RHEL 5 at least\n        # https://github.com/pixelb/scripts/pull/10, so take string up to NUL\n        path = path.split('\\0')[0]\n    except OSError:\n        val = sys.exc_info()[1]\n        if (val.errno == errno.ENOENT or # either kernel thread or process gone\n            val.errno == errno.EPERM or\n            val.errno == errno.EACCES):\n            raise LookupError\n        raise\n\n    if split_args:\n        return ' '.join(cmdline).replace('\\n', ' ')\n    if path.endswith(\" (deleted)\"):\n        path = path[:-10]\n        if os.path.exists(path):\n            path += \" [updated]\"\n        else:\n            #The path could be have prelink stuff so try cmdline\n            #which might have the full path present. This helped for:\n            #/usr/libexec/notification-area-applet.#prelink#.fX7LCT (deleted)\n            if os.path.exists(cmdline[0]):\n                path = cmdline[0] + \" [updated]\"\n            else:\n                path += \" [deleted]\"\n    exe = os.path.basename(path)\n    cmd = proc.open(pid, 'status').readline()[6:-1]\n    if exe.startswith(cmd):\n        cmd = exe #show non truncated version\n        #Note because we show the non truncated name\n        #one can have separated programs as follows:\n        #584.0 KiB +   1.0 MiB =   1.6 MiB    mozilla-thunder (exe -> bash)\n        # 56.0 MiB +  22.2 MiB =  78.2 MiB    mozilla-thunderbird-bin\n    if sys.version_info >= (3,):\n        cmd = cmd.encode(errors='replace').decode()\n    if discriminate_by_pid:\n        cmd = '%s [%d]' % (cmd, pid)\n    return cmd\n\n\n#The following matches \"du -h\" output\n#see also human.py\ndef human(num, power=\"Ki\", units=None):\n    if units is None:\n        powers = [\"Ki\", \"Mi\", \"Gi\", \"Ti\"]\n        while num >= 1000: #4 digits\n            num /= 1024.0\n            power = powers[powers.index(power)+1]\n        return \"%.1f %sB\" % (num, power)\n    else:\n        return \"%.f\" % ((num * 1024) / units)\n\n\ndef cmd_with_count(cmd, count):\n    if count > 1:\n        return \"%s (%u)\" % (cmd, count)\n    else:\n        return cmd\n\n#Warn of possible inaccuracies\n#2 = accurate & can total\n#1 = accurate only considering each process in isolation\n#0 = some shared mem not reported\n#-1= all shared mem not reported\ndef shared_val_accuracy():\n    \"\"\"http://wiki.apache.org/spamassassin/TopSharedMemoryBug\"\"\"\n    kv = kernel_ver()\n    pid = os.getpid()\n    if kv[:2] == (2,4):\n        if proc.open('meminfo').read().find(\"Inact_\") == -1:\n            return 1\n        return 0\n    elif kv[:2] == (2,6):\n        if os.path.exists(proc.path(pid, 'smaps')):\n            if proc.open(pid, 'smaps').read().find(\"Pss:\")!=-1:\n                return 2\n            else:\n                return 1\n        if (2,6,1) <= kv <= (2,6,9):\n            return -1\n        return 0\n    elif kv[0] > 2 and os.path.exists(proc.path(pid, 'smaps')):\n        return 2\n    else:\n        return 1\n\ndef show_shared_val_accuracy( possible_inacc, only_total=False ):\n    level = (\"Warning\",\"Error\")[only_total]\n    if possible_inacc == -1:\n        sys.stderr.write(\n         \"%s: Shared memory is not reported by this system.\\n\" % level\n        )\n        sys.stderr.write(\n         \"Values reported will be too large, and totals are not reported\\n\"\n        )\n    elif possible_inacc == 0:\n        sys.stderr.write(\n         \"%s: Shared memory is not reported accurately by this system.\\n\" % level\n        )\n        sys.stderr.write(\n         \"Values reported could be too large, and totals are not reported\\n\"\n        )\n    elif possible_inacc == 1:\n        sys.stderr.write(\n         \"%s: Shared memory is slightly over-estimated by this system\\n\"\n         \"for each program, so totals are not reported.\\n\" % level\n        )\n    sys.stderr.close()\n    if only_total and possible_inacc != 2:\n        sys.exit(1)\n\n\ndef get_memory_usage(pids_to_show, split_args, discriminate_by_pid,\n                     include_self=False, only_self=False):\n    cmds = {}\n    shareds = {}\n    mem_ids = {}\n    count = {}\n    swaps = {}\n    shared_swaps = {}\n    for pid in os.listdir(proc.path('')):\n        if not pid.isdigit():\n            continue\n        pid = int(pid)\n\n        # Some filters\n        if only_self and pid != our_pid:\n            continue\n        if pid == our_pid and not include_self:\n            continue\n        if pids_to_show is not None and pid not in pids_to_show:\n            continue\n\n        try:\n            cmd = getCmdName(pid, split_args, discriminate_by_pid)\n        except LookupError:\n            #operation not permitted\n            #kernel threads don't have exe links or\n            #process gone\n            continue\n\n        try:\n            private, shared, mem_id, swap, swap_pss = getMemStats(pid)\n        except RuntimeError:\n            continue #process gone\n        if shareds.get(cmd):\n            if have_pss: #add shared portion of PSS together\n                shareds[cmd] += shared\n            elif shareds[cmd] < shared: #just take largest shared val\n                shareds[cmd] = shared\n        else:\n            shareds[cmd] = shared\n        cmds[cmd] = cmds.setdefault(cmd, 0) + private\n        if cmd in count:\n            count[cmd] += 1\n        else:\n            count[cmd] = 1\n        mem_ids.setdefault(cmd, {}).update({mem_id: None})\n\n        # Swap (overcounting for now...)\n        swaps[cmd] = swaps.setdefault(cmd, 0) + swap\n        if have_swap_pss:\n            shared_swaps[cmd] = shared_swaps.setdefault(cmd, 0) + swap_pss\n        else:\n            shared_swaps[cmd] = 0\n\n    # Total swaped mem for each program\n    total_swap = 0\n\n    # Total swaped shared mem for each program\n    total_shared_swap = 0\n\n    # Add shared mem for each program\n    total = 0\n\n    for cmd in cmds:\n        cmd_count = count[cmd]\n        if len(mem_ids[cmd]) == 1 and cmd_count > 1:\n            # Assume this program is using CLONE_VM without CLONE_THREAD\n            # so only account for one of the processes\n            cmds[cmd] /= cmd_count\n            if have_pss:\n                shareds[cmd] /= cmd_count\n        cmds[cmd] = cmds[cmd] + shareds[cmd]\n        total += cmds[cmd]  # valid if PSS available\n        total_swap += swaps[cmd]\n        if have_swap_pss:\n            total_shared_swap += shared_swaps[cmd]\n\n    sorted_cmds = sorted(cmds.items(), key=lambda x:x[1])\n    sorted_cmds = [x for x in sorted_cmds if x[1]]\n\n    return sorted_cmds, shareds, count, total, swaps, shared_swaps, \\\n        total_swap, total_shared_swap\n\n\ndef print_header(show_swap, discriminate_by_pid):\n    output_string = \" Private  +   Shared  =  RAM used\"\n    if show_swap:\n        if have_swap_pss:\n            output_string += \" \" * 5 + \"Shared Swap\"\n        output_string += \"   Swap used\"\n    output_string += \"\\tProgram\"\n    if discriminate_by_pid:\n        output_string += \"[pid]\"\n    output_string += \"\\n\\n\"\n    sys.stdout.write(output_string)\n\n\ndef print_memory_usage(sorted_cmds, shareds, count, total, swaps, total_swap,\n                       shared_swaps, total_shared_swap, show_swap):\n    for cmd in sorted_cmds:\n\n        output_string = \"%9s + %9s = %9s\"\n        output_data = (human(cmd[1]-shareds[cmd[0]]),\n                       human(shareds[cmd[0]]), human(cmd[1]))\n        if show_swap:\n            if have_swap_pss:\n                output_string += \"\\t%9s\"\n                output_data += (human(shared_swaps[cmd[0]]),)\n            output_string += \"   %9s\"\n            output_data += (human(swaps[cmd[0]]),)\n        output_string += \"\\t%s\\n\"\n        output_data += (cmd_with_count(cmd[0], count[cmd[0]]),)\n\n        sys.stdout.write(output_string % output_data)\n\n    if have_pss:\n        if show_swap:\n            if have_swap_pss:\n                sys.stdout.write(\"%s\\n%s%9s%s%9s%s%9s\\n%s\\n\" %\n                                 (\"-\" * 61, \" \" * 24, human(total), \" \" * 7,\n                                  human(total_shared_swap), \" \" * 3,\n                                  human(total_swap), \"=\" * 61))\n            else:\n                sys.stdout.write(\"%s\\n%s%9s%s%9s\\n%s\\n\" %\n                                 (\"-\" * 45, \" \" * 24, human(total), \" \" * 3,\n                                  human(total_swap), \"=\" * 45))\n        else:\n            sys.stdout.write(\"%s\\n%s%9s\\n%s\\n\" %\n                             (\"-\" * 33, \" \" * 24, human(total), \"=\" * 33))\n\n\ndef verify_environment(pids_to_show):\n    if os.geteuid() != 0 and not pids_to_show:\n        sys.stderr.write(\"Sorry, root permission required, or specify pids with -p\\n\")\n        sys.stderr.close()\n        sys.exit(1)\n\n    try:\n        kernel_ver()\n    except (IOError, OSError):\n        val = sys.exc_info()[1]\n        if val.errno == errno.ENOENT:\n            sys.stderr.write(\n              \"Couldn't access \" + proc.path('') + \"\\n\"\n              \"Only GNU/Linux and FreeBSD (with linprocfs) are supported\\n\")\n            sys.exit(2)\n        else:\n            raise\n\ndef main():\n    split_args, pids_to_show, watch, only_total, discriminate_by_pid, \\\n    show_swap = parse_options()\n\n    verify_environment(pids_to_show)\n\n    if not only_total:\n        print_header(show_swap, discriminate_by_pid)\n\n    if watch is not None:\n        try:\n            sorted_cmds = True\n            while sorted_cmds:\n                sorted_cmds, shareds, count, total, swaps, shared_swaps, \\\n                    total_swap, total_shared_swap = \\\n                    get_memory_usage(pids_to_show, split_args,\n                                     discriminate_by_pid)\n                if only_total and have_pss:\n                    sys.stdout.write(human(total, units=1)+'\\n')\n                elif not only_total:\n                    print_memory_usage(sorted_cmds, shareds, count, total,\n                                       swaps, total_swap, shared_swaps,\n                                       total_shared_swap, show_swap)\n\n                sys.stdout.flush()\n                time.sleep(watch)\n            else:\n                sys.stdout.write('Process does not exist anymore.\\n')\n        except KeyboardInterrupt:\n            pass\n    else:\n        # This is the default behavior\n        sorted_cmds, shareds, count, total, swaps, shared_swaps, total_swap, \\\n            total_shared_swap = get_memory_usage(pids_to_show, split_args,\n                                                 discriminate_by_pid)\n        if only_total and have_pss:\n            sys.stdout.write(human(total, units=1)+'\\n')\n        elif not only_total:\n            print_memory_usage(sorted_cmds, shareds, count, total, swaps,\n                               total_swap, shared_swaps, total_shared_swap,\n                               show_swap)\n\n    # We must close explicitly, so that any EPIPE exception\n    # is handled by our excepthook, rather than the default\n    # one which is reenabled after this script finishes.\n    sys.stdout.close()\n\n    vm_accuracy = shared_val_accuracy()\n    show_shared_val_accuracy( vm_accuracy, only_total )\n\nif __name__ == '__main__': main()\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/iri/files/reattach",
    "content": "#!/usr/bin/env python\n# coding=utf-8\nimport sys\nimport argparse\nfrom iota import *\n\n\"\"\"Script to reattach a transaction to tangle\n   source: https://github.com/nuriel77/iri-playbook\n\"\"\"\n\ndef parse_args():\n\n    parser = argparse.ArgumentParser(\n        description='Reattach a transaction.',\n        epilog='Example: ./reattach -x TXHASH'\n               ' -i http://localhost:14265 -m 14 -d 2')\n\n    parser.add_argument('--txhash', '-x', type=str,\n                        required=True,\n                        help='Transaction Hash to reattach')\n\n    parser.add_argument('--depth', '-d', type=int,\n                        default=2,\n                        help='Depth. Default: %(default)s')\n\n    parser.add_argument('--magnitute', '-m', type=int,\n                        default=14,\n                        help='Minimum Weight Magnitute')\n\n    parser.add_argument('--host', '-i', type=str,\n                        default='http://localhost:14265',\n                        help='IRI API endpoint. Default: %(default)s')\n\n    return parser.parse_args()\n\n\ndef run():\n    try:\n        args = parse_args()\n    except Exception as e:\n        sys.stderr.write(\"Error parsing arguments: %s\\n\" % e)\n        sys.exit(1)\n\n    tx_hash = args.txhash\n    min_mag = args.magnitute\n    depth = args.depth\n    api_uri = args.host\n\n    # Create the API instance.\n    api = Iota(api_uri)\n    replayed = api.replay_bundle(tx_hash, depth, min_mag)\n    print(replayed)\n\n\nif __name__ == \"__main__\":\n    run()\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/iri/handlers/main.yml",
    "content": "- name: restart iri\n  systemd:\n    name: iri.service\n    state: restarted\n    enabled: yes\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/iri/tasks/firewalld.yml",
    "content": "- name: allow iri tcp port in firewall\n  firewalld:\n    port: \"{{ iri_tcp_port }}/tcp\"\n    permanent: true\n    state: enabled\n    immediate: yes\n\n- name: allow iri udp port in firewall\n  firewalld:\n    port: \"{{ iri_udp_port }}/udp\"\n    permanent: true\n    state: enabled\n    immediate: yes\n\n- name: allow iri api port in firewall\n  firewalld:\n    port: \"{{ iri_api_port }}/tcp\"\n    permanent: true\n    state: enabled\n    immediate: yes\n  when: api_port_remote is defined and api_port_remote\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/iri/tasks/iri.yml",
    "content": "- name: set variables centos/redhat\n  set_fact:\n    systemd_dir: /usr/lib/systemd/system\n    config_dir: /etc/sysconfig\n  when: ansible_distribution == 'CentOS'\n\n- name: set variables debian/ubuntu\n  set_fact:\n    systemd_dir: /lib/systemd/system\n    config_dir: /etc/default\n  when: ansible_distribution == 'Ubuntu'\n\n# TODO: set 0.8 to be configured in iri.yml variable file\n- name: set memory limit for java-iri\n  set_fact:\n    iri_java_mem: \"{{ (ansible_memtotal_mb * 0.8)|int|abs }}m\"\n  when: memory_autoset is defined and memory_autoset\n  tags:\n    - mem_override\n\n- name: add user to run iri as\n  user:\n    name: \"{{ iri_username }}\"\n    shell: /sbin/nologin\n    createhome: no\n    home: \"{{ iri_basedir }}\"\n  tags:\n    - iri_user\n\n- name: get iri user uid\n  shell: \"echo -n $(id -u {{ iri_username }})\"\n  changed_when: false\n  register: iri_uid\n\n- name: delete iri data basedir\n  block:\n    - name: stop iri\n      systemd:\n        name: iri.service\n        state: stopped\n      false_when: false\n\n    - name: remove basedir\n      file:\n        path: \"{{ iri_basedir }}\"\n        state: absent\n  when: remove_iri_basedir is defined and remove_iri_basedir\n\n- name: ensure iri basedir ownership and permissions\n  file:\n    path: \"{{ iri_basedir }}\"\n    state: directory\n    mode: 0700\n    owner: \"{{ iri_username }}\"\n    group: \"{{ iri_username }}\"\n\n- name: ensure iri basedir ownership and permissions\n  file:\n    path: \"{{ iri_basedir }}\"\n    state: directory\n    mode: 0700\n    owner: \"{{ iri_username }}\"\n    group: \"{{ iri_username }}\"\n\n- name: ensure iri config dir exists\n  file:\n    path: \"{{ iri_configdir }}\"\n    state: directory\n    mode: 0700\n    owner: \"{{ iri_username }}\"\n    group: \"{{ iri_username }}\"\n\n- name: copy utility scripts\n  copy:\n    src: \"files/{{ item }}\"\n    dest: \"/usr/bin/{{ item }}\"\n    mode: 0755\n  with_items:\n    - nbctl\n    - reattach\n    - ps_mem\n  tags:\n    - scripts\n\n- name: copy iri systemd file\n  template:\n    src: files/iri.service\n    dest: \"{{ systemd_dir }}/iri.service\"\n  notify:\n    - reload systemd\n\n- name: copy iri config file\n  template:\n    src: templates/iri.ini\n    dest: \"{{ iri_configdir }}/iri.ini\"\n    owner: \"{{ iri_username }}\"\n    group: \"{{ iri_username }}\"\n    force: no\n  notify:\n    - restart iri\n\n- name: Create iri container\n  docker_container:\n    name: iri\n    state: present\n    user: \"{{ iri_uid.stdout }}\"\n    restart_policy: unless-stopped\n    network_mode: host\n    hostname: iri\n    image: \"{{ iri_image }}:{{ iri_tag }}\"\n    ports:\n      - \"{{ iri_api_port }}:{{ iri_api_port }}\"\n      - \"{{ iri_tcp_port }}:{{ iri_tcp_port }}\"\n      - \"{{ iri_udp_port }}:{{ iri_udp_port }}/udp\"\n    volumes:\n      - \"{{ iri_basedir }}:/iri/target:Z\"\n      - \"{{ iri_configdir }}:/iri/conf:ro,Z\"\n    env:\n      IRI_CONFIG: \"/iri/conf/iri.ini\"\n      INIT_MEMORY: \"{{ iri_init_java_mem }}\" \n      MAX_MEMORY: \"{{ iri_java_mem }}\"\n  tags:\n    - iri_create_container\n  notify:\n    - restart iri\n\n- name: flush handlers\n  meta: flush_handlers\n\n- name: ensure iri started and enabled\n  systemd:\n    daemon_reload: true\n    name: iri.service\n    state: started\n    enabled: true\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/iri/tasks/main.yml",
    "content": "- import_tasks: role.yml\n  tags:\n    - iri_role\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/iri/tasks/role.yml",
    "content": "- import_tasks: ufw.yml\n  tags:\n    - iri_ufw\n  when: ansible_distribution == 'Ubuntu'\n\n- import_tasks: firewalld.yml\n  tags:\n    - iri_firewalld\n  when: ansible_distribution == 'CentOS'\n\n- import_tasks: iri.yml\n  tags:\n    - iri_install\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/iri/tasks/ufw.yml",
    "content": "- name: allow iri tcp port in firewall\n  ufw:\n    rule: allow\n    direction: in\n    proto: tcp\n    port: \"{{ iri_tcp_port }}\"\n\n- name: allow iri udp port in firewall\n  ufw:\n    rule: allow\n    direction: in\n    proto: udp\n    port: \"{{ iri_udp_port }}\"\n\n- name: allow iri api port in firewall\n  ufw:\n    rule: allow\n    direction: in\n    proto: tcp\n    port: \"{{ iri_api_port }}\"\n  when: api_port_remote is defined and api_port_remote\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/iri/templates/iri.ini",
    "content": "[IRI]\nPORT = {{ iri_api_port }}\nUDP_RECEIVER_PORT = {{ iri_udp_port }}\nTCP_RECEIVER_PORT = {{ iri_tcp_port }}\nIXI_DIR = ixi\nHEADLESS = true\nDEBUG = false\nDB_PATH = /iri/target\nNEIGHBORS = udp://my.favorite.com:15600\nREMOTE_LIMIT_API = \"removeNeighbors, addNeighbors, interruptAttachingToTangle, attachToTangle, getNeighbors, setApiRateLimit\"\n{% if api_port_remote is defined and api_port_remote %}API_HOST = 0.0.0.0{% endif %}\n\n# Uncommend this line and set user and password\n# in the format: `user:password` to password protect\n# the IRI API. Change requires restart of iri.\n# If enabled, the API will have to be called using\n# basic auth. For example, with curl:\n# curl http://user:password@localhost:14265 ...\n#REMOTE_AUTH = iota:password\n\n# set max requests value\n#MAX_REQUESTS_LIST = 9999\n\n# set max find transactions value\n#MAX_FIND_TRANSACTIONS = 100000\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/nelson/files/nelson.service",
    "content": "[Unit]\nDescription=Nelson Docker Container\nRequires=docker.service\nAfter=docker.service\n\n[Service]\nRestart=on-failure\nRestartSec=10\nExecStart=/usr/bin/docker start -a %p\nExecStop=-/usr/bin/docker stop -t 2 %p\n\n[Install]\nWantedBy=multi-user.target\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/nelson/handlers/main.yml",
    "content": "- name: restart nelson\n  systemd:\n    name: nelson.service\n    enabled: true\n    state: restarted\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/nelson/tasks/firewall.yml",
    "content": "- name: allow nelson tcp port in firewall\n  firewalld:\n    port: \"{{ nelson_tcp_port }}/tcp\"\n    permanent: true\n    state: enabled\n    immediate: yes\n  when: ansible_distribution == 'CentOS'\n\n- name: allow nelson tcp port in firewall\n  ufw:\n    rule: allow\n    direction: in\n    proto: tcp\n    port: \"{{ nelson_tcp_port }}\"\n  when: ansible_distribution == 'Ubuntu'\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/nelson/tasks/main.yml",
    "content": "- import_tasks: role.yml\n  tags:\n    - nelson_role\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/nelson/tasks/nelson.yml",
    "content": "- name: set variables centos/redhat\n  set_fact:\n    systemd_dir: /usr/lib/systemd/system\n    config_dir: /etc/sysconfig\n  when: ansible_distribution == 'CentOS'\n\n- name: set variables debian/ubuntu\n  set_fact:\n    systemd_dir: /lib/systemd/system\n    config_dir: /etc/default\n  when: ansible_distribution == 'Ubuntu'\n\n- name: add nelson user\n  user:\n    name: \"{{ nelson_username }}\"\n    shell: /sbin/nologin\n    createhome: no\n    home: \"{{ nelson_datadir }}\"\n  tags:\n    - nelson_user\n\n- name: get nelson user uid\n  shell: \"echo -n $(id -u {{ nelson_username }})\" \n  changed_when: false\n  register: nelson_uid\n\n- name: create nelson data and config directory\n  file:\n    path: \"{{ item }}\"\n    state: directory\n    owner: \"{{ nelson_username }}\"\n    group: \"{{ nelson_username }}\"\n    mode: 0700\n  with_items:\n    - \"{{ nelson_configdir }}\"\n    - \"{{ nelson_datadir }}\"\n\n- name: copy nelson config\n  template:\n    src: \"templates/config.ini.j2\"\n    dest: \"{{ nelson_configdir }}/config.ini\"\n    owner: \"{{ nelson_username }}\"\n    group: \"{{ nelson_username }}\"\n    mode: 0600\n    force: no\n  notify:\n    - restart nelson\n\n- name: copy systemd service file\n  template:\n    src: files/nelson.service\n    dest: \"{{ systemd_dir }}/nelson.service\"\n  notify:\n    - reload systemd\n\n- name: Create nelson container\n  docker_container:\n    name: nelson\n    state: present\n    user: \"{{ nelson_uid.stdout }}\"\n    restart_policy: unless-stopped\n    network_mode: host\n    hostname: nelson\n    image: \"{{ nelson_image }}:{{ nelson_tag }}\"\n    ports:\n      - \"{{ nelson_api_port }}:{{ nelson_api_port }}\"\n      - \"{{ nelson_tcp_port }}:{{ nelson_tcp_port }}\"\n    volumes:\n      - \"{{ nelson_datadir }}:/var/lib/nelson/data:Z\"\n      - \"{{ nelson_configdir }}:/etc/nelson:ro,Z\"\n    env:\n      NELSON_CONFIG: \"{{ nelson_configdir }}/config.ini\"\n  tags:\n    - nelson_create_container\n  notify:\n    - restart nelson\n\n- name: ensure nelson started and enabled\n  systemd:\n    name: nelson.service\n    state: started\n    enabled: yes\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/nelson/tasks/role.yml",
    "content": "- import_tasks: firewall.yml\n  tags:\n    - nelson_firewall\n\n- import_tasks: nelson.yml\n  tags:\n    - nelson_install\n"
  },
  {
    "path": "contrib/ansible-playbook/roles/nelson/templates/config.ini.j2",
    "content": "[nelson]\ncycleInterval = 60\nepochInterval = 300\napiPort = {{ nelson_api_port }}\napiHostname = {{ nelson_iri_host }}\nport = {{ nelson_tcp_port }}\nIRIHostname = {{ nelson_iri_host }}\nIRIProtocol = any\nIRIPort = {{ iri_api_port }}\nTCPPort = {{ iri_tcp_port }}\nUDPPort = {{ iri_udp_port }}\ndataPath = {{ nelson_datadir }}/data/neighbors.db\n; maximal incoming connections. Please do not set below this limit:\nincomingMax = 5\n; maximal outgoing connections. Only set below this limit, if you have trusted, manual neighbors:\noutgoingMax = 4\nisMaster = false\nsilent = false\ngui = false\n; use automatic service to download latest initial nodes\ngetNeighbors = https://raw.githubusercontent.com/SemkoDev/nelson.cli/master/ENTRYNODES\n; add as many initial nelson neighbors, as you like\nneighbors[] = mainnet.deviota.com/16600\nneighbors[] = mainnet2.deviota.com/16600\nneighbors[] = mainnet3.deviota.com/16600\nneighbors[] = iotairi.tt-tec.net/16600\n"
  },
  {
    "path": "contrib/ansible-playbook/site.yml",
    "content": "---\n- hosts: fullnode\n  gather_facts: true\n  become: true\n  become_method: sudo\n  roles:\n    - common\n    - iri\n    - nelson\n"
  },
  {
    "path": "dist/api/index.js",
    "content": "'use strict';\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nvar express = require('express');\nvar helmet = require('helmet');\nvar bodyParser = require('body-parser');\nvar basicAuth = require('express-basic-auth');\n\nvar _require = require('./node'),\n    getNodeStats = _require.getNodeStats,\n    getSummary = _require.getSummary;\n\nvar _require2 = require('./peer'),\n    getPeerStats = _require2.getPeerStats;\n\nvar _require3 = require('./webhooks'),\n    startWebhooks = _require3.startWebhooks;\n\nvar DEFAULT_OPTIONS = {\n    node: null,\n    webhooks: [],\n    webhookInterval: 30,\n    username: null,\n    password: null,\n    apiPort: 18600,\n    apiHostname: '127.0.0.1'\n};\n\n/**\n * Creates an Express APP instance, also starts regular webhooks callbacks.\n * @param options\n * @returns {*|Function}\n */\nfunction createAPI(options) {\n    var opts = _extends({}, DEFAULT_OPTIONS, options);\n\n    // Start webhook callbacks\n    if (opts.webhooks && opts.webhooks.length) {\n        startWebhooks(opts.node, opts.webhooks, opts.webhookInterval);\n    }\n\n    // Start API server\n    var app = express();\n    app.set('node', opts.node);\n\n    // Basic app protection\n    app.use(helmet());\n\n    // Enable basic HTTP Auth\n    if (opts.username && opts.password) {\n        app.use(basicAuth({\n            users: _defineProperty({}, opts.username, opts.password)\n        }));\n    }\n\n    // parse application/x-www-form-urlencoded\n    app.use(bodyParser.urlencoded({ extended: false }));\n\n    // parse application/json\n    app.use(bodyParser.json());\n\n    //////////////////////// ENDPOINTS ////////////////////////\n\n    app.get('/', function (req, res) {\n        res.json(getNodeStats(opts.node));\n    });\n\n    app.get('/peer-stats', function (req, res) {\n        res.json(getSummary(opts.node));\n    });\n\n    app.get('/peers', function (req, res) {\n        res.json(opts.node.list.all().map(getPeerStats));\n    });\n\n    return app.listen(opts.apiPort, opts.apiHostname);\n}\n\nmodule.exports = {\n    createAPI: createAPI,\n    DEFAULT_OPTIONS: DEFAULT_OPTIONS\n};"
  },
  {
    "path": "dist/api/node.js",
    "content": "'use strict';\n\nvar _require = require('./peer'),\n    getPeerStats = _require.getPeerStats;\n\nvar version = require('../../package.json').version;\n\n/**\n * Returns summary of the node stats\n * @param {Node} node\n * @returns {{newNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}, activeNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}}}\n */\nfunction getSummary(node) {\n    var now = new Date();\n    var hour = 3600000;\n    var hourAgo = new Date(now - hour);\n    var fourAgo = new Date(now - hour * 4);\n    var twelveAgo = new Date(now - hour * 12);\n    var dayAgo = new Date(now - hour * 24);\n    var weekAgo = new Date(now - hour * 24 * 7);\n    return {\n        newNodes: {\n            hourAgo: node.list.all().filter(function (p) {\n                return p.data.dateCreated >= hourAgo;\n            }).length,\n            fourAgo: node.list.all().filter(function (p) {\n                return p.data.dateCreated >= fourAgo;\n            }).length,\n            twelveAgo: node.list.all().filter(function (p) {\n                return p.data.dateCreated >= twelveAgo;\n            }).length,\n            dayAgo: node.list.all().filter(function (p) {\n                return p.data.dateCreated >= dayAgo;\n            }).length,\n            weekAgo: node.list.all().filter(function (p) {\n                return p.data.dateCreated >= weekAgo;\n            }).length\n        },\n        activeNodes: {\n            hourAgo: node.list.all().filter(function (p) {\n                return p.data.dateLastConnected >= hourAgo;\n            }).length,\n            fourAgo: node.list.all().filter(function (p) {\n                return p.data.dateLastConnected >= fourAgo;\n            }).length,\n            twelveAgo: node.list.all().filter(function (p) {\n                return p.data.dateLastConnected >= twelveAgo;\n            }).length,\n            dayAgo: node.list.all().filter(function (p) {\n                return p.data.dateLastConnected >= dayAgo;\n            }).length,\n            weekAgo: node.list.all().filter(function (p) {\n                return p.data.dateLastConnected >= weekAgo;\n            }).length\n        }\n    };\n}\n\n/**\n * Returns clean node stats to be used in the API\n * @param {Node} node\n * @returns {{name, version, ready: (boolean|*|null), isIRIHealthy: (*|boolean), iriStats: *, peerStats: {newNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}, activeNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}}, totalPeers, connectedPeers: Array, config: {cycleInterval: (Command.opts.cycleInterval|*), epochInterval: (Command.opts.epochInterval|*), beatInterval: (Command.opts.beatInterval|*), dataPath: (Command.opts.dataPath|*), port: (Command.opts.port|*), apiPort: (Command.opts.apiPort|*), IRIPort: (Command.opts.IRIPort|*), TCPPort: (Command.opts.TCPPort|*), UDPPort: (Command.opts.UDPPort|*), IRIProtocol: (Command.opts.IRIProtocol|*), isMaster: (Command.opts.isMaster|*), temporary: (Command.opts.temporary|*)}, heart: {lastCycle: (heart.lastCycle|Heart.lastCycle|_require2.Heart.lastCycle), lastEpoch: (heart.lastEpoch|Heart.lastEpoch|_require2.Heart.lastEpoch), personality: (heart.personality|Heart.personality|_require2.Heart.personality), currentCycle: (heart.currentCycle|Heart.currentCycle|_require2.Heart.currentCycle), currentEpoch: (heart.currentEpoch|Heart.currentEpoch|_require2.Heart.currentEpoch), startDate: (heart.startDate|Heart.startDate|_require2.Heart.startDate)}}}\n */\nfunction getNodeStats(node) {\n    var _node$opts = node.opts,\n        cycleInterval = _node$opts.cycleInterval,\n        epochInterval = _node$opts.epochInterval,\n        beatInterval = _node$opts.beatInterval,\n        dataPath = _node$opts.dataPath,\n        port = _node$opts.port,\n        apiPort = _node$opts.apiPort,\n        IRIPort = _node$opts.IRIPort,\n        TCPPort = _node$opts.TCPPort,\n        UDPPort = _node$opts.UDPPort,\n        isMaster = _node$opts.isMaster,\n        IRIProtocol = _node$opts.IRIProtocol,\n        temporary = _node$opts.temporary;\n    var _node$heart = node.heart,\n        lastCycle = _node$heart.lastCycle,\n        lastEpoch = _node$heart.lastEpoch,\n        personality = _node$heart.personality,\n        currentCycle = _node$heart.currentCycle,\n        currentEpoch = _node$heart.currentEpoch,\n        startDate = _node$heart.startDate;\n\n    var totalPeers = node.list.all().length;\n    var isIRIHealthy = node.iri && node.iri.isHealthy;\n    var iriStats = node.iri && node.iri.iriStats;\n    var connectedPeers = Array.from(node.sockets.keys()).filter(function (p) {\n        return node.sockets.get(p).readyState === 1;\n    }).map(getPeerStats);\n\n    return {\n        name: node.opts.name,\n        version: version,\n        ready: node._ready,\n        isIRIHealthy: isIRIHealthy,\n        iriStats: iriStats,\n        peerStats: getSummary(node),\n        totalPeers: totalPeers,\n        connectedPeers: connectedPeers,\n        config: {\n            cycleInterval: cycleInterval,\n            epochInterval: epochInterval,\n            beatInterval: beatInterval,\n            dataPath: dataPath,\n            port: port,\n            apiPort: apiPort,\n            IRIPort: IRIPort,\n            TCPPort: TCPPort,\n            UDPPort: UDPPort,\n            IRIProtocol: IRIProtocol,\n            isMaster: isMaster,\n            temporary: temporary\n        },\n        heart: {\n            lastCycle: lastCycle,\n            lastEpoch: lastEpoch,\n            personality: personality,\n            currentCycle: currentCycle,\n            currentEpoch: currentEpoch,\n            startDate: startDate\n        }\n    };\n}\n\nmodule.exports = {\n    getSummary: getSummary,\n    getNodeStats: getNodeStats\n};"
  },
  {
    "path": "dist/api/peer.js",
    "content": "\"use strict\";\n\n/**\n * Returns a clean Peer object that can be used in the API\n * @param {Peer} peer\n * @returns {{name, hostname, ip, port, TCPPort, UDPPort, protocol, IRIProtocol, seen, connected, tried, weight, dateTried, dateLastConnected, dateCreated, isTrusted, lastConnections}}\n */\nfunction getPeerStats(peer) {\n    var _peer$data = peer.data,\n        name = _peer$data.name,\n        hostname = _peer$data.hostname,\n        ip = _peer$data.ip,\n        port = _peer$data.port,\n        TCPPort = _peer$data.TCPPort,\n        UDPPort = _peer$data.UDPPort,\n        protocol = _peer$data.protocol,\n        seen = _peer$data.seen,\n        connected = _peer$data.connected,\n        tried = _peer$data.tried,\n        weight = _peer$data.weight,\n        dateTried = _peer$data.dateTried,\n        dateLastConnected = _peer$data.dateLastConnected,\n        dateCreated = _peer$data.dateCreated,\n        IRIProtocol = _peer$data.IRIProtocol,\n        isTrusted = _peer$data.isTrusted,\n        lastConnections = _peer$data.lastConnections;\n\n    return {\n        name: name,\n        hostname: hostname,\n        ip: ip,\n        port: port,\n        TCPPort: TCPPort,\n        UDPPort: UDPPort,\n        protocol: protocol,\n        IRIProtocol: IRIProtocol,\n        seen: seen,\n        connected: connected,\n        tried: tried,\n        weight: weight,\n        dateTried: dateTried,\n        dateLastConnected: dateLastConnected,\n        dateCreated: dateCreated,\n        isTrusted: isTrusted,\n        lastConnections: lastConnections\n    };\n}\n\nmodule.exports = {\n    getPeerStats: getPeerStats\n};"
  },
  {
    "path": "dist/api/utils.js",
    "content": "\"use strict\";"
  },
  {
    "path": "dist/api/webhooks.js",
    "content": "'use strict';\n\nvar request = require('request');\n\nvar _require = require('./node'),\n    getNodeStats = _require.getNodeStats;\n\nfunction startWebhooks(node, webhooks, webhookInterval) {\n    var interval = setInterval(function () {\n        webhooks.forEach(function (uri) {\n            return request({\n                uri: uri,\n                method: 'POST',\n                json: getNodeStats(node)\n            }, function (err) {\n                if (err) {\n                    node.log(('Webhook returned error: ' + uri).yellow);\n                }\n            });\n        });\n    }, webhookInterval * 1000);\n    return {\n        stop: function stop() {\n            clearInterval(interval);\n        }\n    };\n}\n\nmodule.exports = {\n    startWebhooks: startWebhooks\n};"
  },
  {
    "path": "dist/index.js",
    "content": "'use strict';\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nfunction _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }\n\nrequire('colors');\nvar request = require('request');\nvar terminal = require('./node/tools/terminal');\nvar node = require('./node').node;\nvar api = require('./api/index');\nvar utils = require('./node').utils;\n\n// Some general TODOs:\n// TODO: add linting\n// TODO: add editor config\n\nmodule.exports = _extends({\n    initNode: function initNode(opts) {\n        var init = function init(options) {\n            var _node = new node.Node(options);\n            var terminate = function terminate() {\n                return _node.end().then(function () {\n                    process.exit(0);\n                });\n            };\n\n            process.on('SIGINT', terminate);\n            process.on('SIGTERM', terminate);\n            opts.gui && terminal.init(opts.name, utils.getVersion(), terminate);\n\n            _node.start().then(function (n) {\n                api.createAPI({\n                    node: n,\n                    webhooks: opts.webhooks,\n                    webhookInterval: opts.webhookInterval,\n                    apiPort: opts.apiPort,\n                    apiHostname: opts.apiHostname,\n                    username: opts.apiAuth && opts.apiAuth.username,\n                    password: opts.apiAuth && opts.apiAuth.password\n                });\n                terminal.ports(n.opts);\n                n.log(('Nelson v.' + utils.getVersion() + ' initialized').green.bold);\n            });\n        };\n\n        if (opts.getNeighbors) {\n            if (typeof opts.getNeighbors === 'boolean') {\n                opts.getNeighbors = 'https://raw.githubusercontent.com/SemkoDev/nelson.cli/master/ENTRYNODES';\n            }\n            var neighbors = [];\n            request(opts.getNeighbors, function (err, resp, body) {\n                if (err) {\n                    throw err;\n                }\n                neighbors = body.split('\\n').map(function (str) {\n                    if (!str || !str.length) {\n                        return null;\n                    }\n                    if (utils.validNeighbor(str)) {\n                        console.log('Downloaded entry neighbor:', str);\n                        return str;\n                    } else {\n                        console.log('Wrong entry neighbor format:', str);\n                        return null;\n                    }\n                }).filter(function (n) {\n                    return n;\n                });\n                opts.neighbors = [].concat(_toConsumableArray(opts.neighbors ? opts.neighbors : []), _toConsumableArray(neighbors));\n                init(opts);\n            });\n        } else {\n            init(opts);\n        }\n    }\n}, node);"
  },
  {
    "path": "dist/nelson.js",
    "content": "#!/usr/bin/env node\n'use strict';\n\nrequire('colors');\n\nvar ini = require('ini');\nvar fs = require('fs');\n\nvar _require = require('url'),\n    URL = _require.URL;\n\nvar program = require('commander');\n\nvar _require2 = require('./index'),\n    initNode = _require2.initNode;\n\nvar _require3 = require('./node/node'),\n    DEFAULT_OPTIONS = _require3.DEFAULT_OPTIONS;\n\nvar _require4 = require('./node/peer'),\n    PROTOCOLS = _require4.PROTOCOLS;\n\nvar _require5 = require('./node/peer-list'),\n    DEFAULT_LIST_OPTIONS = _require5.DEFAULT_OPTIONS;\n\nvar _require6 = require('./api/index'),\n    DEFAULT_API_OPTIONS = _require6.DEFAULT_OPTIONS;\n\nvar version = require('../package.json').version;\n\nvar parseNeighbors = function parseNeighbors(val) {\n    return val.split(' ');\n};\nvar parseURLs = function parseURLs(val) {\n    return val.split(' ').map(function (v) {\n        return new URL(v);\n    }).map(function (u) {\n        return u.href;\n    });\n};\nvar parseProtocol = function parseProtocol(val) {\n    var lower = val.toLowerCase();\n    return PROTOCOLS.includes(lower) ? lower : DEFAULT_OPTIONS.IRIProtocol;\n};\nvar parseNumber = function parseNumber(v) {\n    return parseInt(v);\n};\nvar parseAuth = function parseAuth(v) {\n    var tokens = v.split(':');\n    if (!tokens.length === 2) {\n        throw new Error('Wrong apiAuth format! Use: \"username.password\"');\n    }\n    if (!tokens[0].length) {\n        throw new Error('apiAuth username not provided!');\n    }\n    if (!tokens[1].length) {\n        throw new Error('apiAuth password not provided!');\n    }\n    return { username: tokens[0], password: tokens[1] };\n};\n\nprogram.version(version).option('--name [value]', 'Name of your node instance', DEFAULT_OPTIONS.name).option('-n, --neighbors [value]', 'Trusted neighbors', parseNeighbors, []).option('--getNeighbors [url]', 'Download default set of neighbors', false).option('-c, --cycleInterval [value]', 'Cycle interval in seconds', parseNumber, DEFAULT_OPTIONS.cycleInterval).option('-e, --epochInterval [value]', 'Epoch interval in seconds', parseNumber, DEFAULT_OPTIONS.epochInterval).option('--incomingMax [value]', 'Maximal incoming connection slots', parseNumber, DEFAULT_OPTIONS.incomingMax).option('--outgoingMax [value]', 'Maximal outgoing connection slots', parseNumber, DEFAULT_OPTIONS.outgoingMax).option('--lazyLimit [value]', 'Seconds after which neighbor is dropped for not having provided any new TXs', parseNumber, DEFAULT_OPTIONS.lazyLimit).option('--lazyTimesLimit [value]', 'How many consecutive times a lazy neighbor can connect before getting penalized', parseNumber, DEFAULT_OPTIONS.lazyTimesLimit).option('--apiAuth [value]', 'Nelson API username:password', parseAuth, null).option('-a, --apiPort [value]', 'Nelson API port', parseNumber, DEFAULT_API_OPTIONS.apiPort).option('-o, --apiHostname [value]', 'Nelson API hostname', DEFAULT_API_OPTIONS.apiHostname).option('-w, --webhooks [value]', 'Nelson API webhook URLs', parseURLs, DEFAULT_API_OPTIONS.webhooks).option('--webhookInterval [value]', 'Webhooks callback interval in seconds', parseNumber, DEFAULT_API_OPTIONS.webhookInterval).option('-p, --port [value]', 'Nelson port', parseNumber, DEFAULT_OPTIONS.port).option('-r, --IRIHostname [value]', 'IRI API hostname', DEFAULT_OPTIONS.IRIHostname).option('-i, --IRIPort [value]', 'IRI API port', parseNumber, DEFAULT_OPTIONS.IRIPort).option('-t, --TCPPort [value]', 'IRI TCP port', parseNumber, DEFAULT_OPTIONS.TCPPort).option('-u, --UDPPort [value]', 'IRI UDP port', parseNumber, DEFAULT_OPTIONS.UDPPort).option('--IRIProtocol [value]', 'IRI protocol to use: udp, tcp, prefertcp, preferudp or any', parseProtocol, DEFAULT_OPTIONS.IRIProtocol).option('-d, --dataPath [value]', 'Peer database path', DEFAULT_LIST_OPTIONS.dataPath).option('-m, --isMaster [value]', 'Is a master node', false).option('-s, --silent [value]', 'Silent', false).option('-g, --gui [value]', 'GUI', false).option('--temporary [value]', 'Create a temporary node', false).option('--config [value]', 'Config file path', null).parse(process.argv);\n\nvar configPath = process.env.NELSON_CONFIG || program.config;\n\ninitNode(configPath ? ini.parse(fs.readFileSync(configPath, 'utf-8')).nelson : program);"
  },
  {
    "path": "dist/node/__mocks__/iri.js",
    "content": "'use strict';\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\nvar req = require.requireActual ? require.requireActual : require;\n\nvar _req = req('../iri'),\n    BaseIRI = _req.IRI,\n    DEFAULT_OPTIONS = _req.DEFAULT_OPTIONS;\n\n/**\n * Class responsible to RUN and communicate with local IRI instance\n * @class\n */\n\n\nvar IRI = function (_BaseIRI) {\n    _inherits(IRI, _BaseIRI);\n\n    function IRI() {\n        _classCallCheck(this, IRI);\n\n        return _possibleConstructorReturn(this, (IRI.__proto__ || Object.getPrototypeOf(IRI)).apply(this, arguments));\n    }\n\n    _createClass(IRI, [{\n        key: 'start',\n\n\n        /**\n         * Starts the IRI process, returning self on success.\n         * @returns {Promise<IRI>}\n         */\n        value: function start() {\n            var _this2 = this;\n\n            return new Promise(function (resolve) {\n                _this2._isStarted = true;\n                _this2.isHealthy = true;\n                _this2.ticker = setInterval(_this2._tick, 15000);\n                _this2.getStats().then(function () {\n                    return resolve(_this2);\n                });\n            });\n        }\n\n        /**\n         * Removes a list of neighbors from IRI, except static neighbors. Returns list of removed peers.\n         * @param {Peer[]} peers\n         * @returns {Promise<Peer[]>}\n         */\n\n    }, {\n        key: 'removeNeighbors',\n        value: function removeNeighbors(peers) {\n            if (!this.isAvailable()) {\n                return Promise.reject();\n            }\n\n            return new Promise(function (resolve) {\n                resolve(peers);\n            });\n        }\n\n        /**\n         * Adds a list of peers to IRI.\n         * @param {Peer[]} peers\n         * @returns {Promise<Peer[]>}\n         */\n\n    }, {\n        key: 'addNeighbors',\n        value: function addNeighbors(peers) {\n            if (!this.isAvailable()) {\n                return Promise.reject();\n            }\n\n            return new Promise(function (resolve) {\n                resolve(peers);\n            });\n        }\n\n        /**\n         * Cleans up any orphans from the IRI\n         * @param {Peer[]} peers\n         * @returns {Promise<URL[]>}\n         */\n\n    }, {\n        key: 'cleanupNeighbors',\n        value: function cleanupNeighbors(peers) {\n            if (!this.isAvailable()) {\n                return Promise.reject();\n            }\n            return new Promise(function (resolve) {\n                resolve([]);\n            });\n        }\n\n        /**\n         * Updates the list of neighbors at the IRI backend. Removes all neighbors, replacing them with\n         * the newly provided neighbors.\n         * @param {Peer[]} peers\n         * @returns {Promise<Peer[]>}\n         */\n\n    }, {\n        key: 'updateNeighbors',\n        value: function updateNeighbors(peers) {\n            var _this3 = this;\n\n            if (!this.isAvailable()) {\n                return Promise.reject();\n            }\n\n            if (!peers || !peers.length) {\n                return Promise.resolve([]);\n            }\n\n            return new Promise(function (resolve, reject) {\n                var addNeighbors = function addNeighbors() {\n                    _this3.addNeighbors(peers).then(resolve).catch(reject);\n                };\n\n                addNeighbors();\n            });\n        }\n\n        /**\n         * Removes all IRI neighbors, except static neighbors.\n         * @returns {Promise}\n         */\n\n    }, {\n        key: 'removeAllNeighbors',\n        value: function removeAllNeighbors() {\n            if (!this.isAvailable()) {\n                return Promise.reject();\n            }\n\n            return new Promise(function (resolve) {\n                resolve();\n            });\n        }\n\n        /**\n         * Returns IRI node info\n         * @returns {Promise<object>}\n         */\n\n    }, {\n        key: 'getStats',\n        value: function getStats() {\n            var _this4 = this;\n\n            return new Promise(function (resolve) {\n                _this4.iriStats = { mock: true };\n                resolve(_this4.iriStats);\n            });\n        }\n    }, {\n        key: '_tick',\n        value: function _tick() {\n            var onHealthCheck = this.opts.onHealthCheck;\n\n            this.getStats().then(function () {\n                onHealthCheck(true, []);\n            });\n        }\n    }]);\n\n    return IRI;\n}(BaseIRI);\n\nIRI.isMocked = true;\n\nmodule.exports = {\n    IRI: IRI,\n    DEFAULT_OPTIONS: DEFAULT_OPTIONS\n};"
  },
  {
    "path": "dist/node/__mocks__/node.js",
    "content": "'use strict';\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\nvar _require = require('../node'),\n    BaseNode = _require.Node,\n    DEFAULT_NODE_OPTIONS = _require.DEFAULT_OPTIONS;\n\nvar _require2 = require('../tools/utils'),\n    getRandomInt = _require2.getRandomInt;\n\nvar _require3 = require('./iri'),\n    IRI = _require3.IRI;\n\nvar DEFAULT_OPTIONS = _extends({}, DEFAULT_NODE_OPTIONS, {\n    localNodes: true,\n    beatInterval: 2,\n    cycleInterval: 3,\n    epochInterval: 30,\n    lazyLimit: 6,\n    testnet: true,\n    temporary: true\n});\n\n/**\n * This is a mock for the \"real\" node. What it does are several things:\n *\n * 1. Mock away IRI backend so we do not start it. We just want to test the P2P functionality.\n * 2. Create a separate neighbor database for each node.\n * 3. Report stats to the parent process\n *\n * @class Node\n */\n\nvar Node = function (_BaseNode) {\n    _inherits(Node, _BaseNode);\n\n    function Node(options) {\n        _classCallCheck(this, Node);\n\n        var _this = _possibleConstructorReturn(this, (Node.__proto__ || Object.getPrototypeOf(Node)).call(this, _extends({}, DEFAULT_OPTIONS, options)));\n\n        _this.sendStats = _this.sendStats.bind(_this);\n        setInterval(_this.sendStats, 1000);\n        return _this;\n    }\n\n    _createClass(Node, [{\n        key: '_getIRI',\n        value: function _getIRI() {\n            var _this2 = this;\n\n            var _opts = this.opts,\n                APIPort = _opts.APIPort,\n                TCPPort = _opts.TCPPort,\n                UDPPort = _opts.UDPPort,\n                testnet = _opts.testnet,\n                silent = _opts.silent,\n                temporary = _opts.temporary;\n\n\n            return new IRI({\n                APIPort: APIPort, TCPPort: TCPPort, UDPPort: UDPPort, testnet: testnet, silent: silent, temporary: temporary,\n                logIdent: this.opts.port + '::IRI'\n            }).start().then(function (iri) {\n                _this2.iri = iri;\n                return iri;\n            });\n        }\n    }, {\n        key: '_setPublicIP',\n        value: function _setPublicIP() {\n            this.ipv4 = 'localhost';\n            return Promise.resolve(0);\n        }\n    }, {\n        key: '_onIRIHealth',\n        value: function _onIRIHealth() {\n            Array.from(this.sockets.keys()).forEach(function (peer) {\n                peer.updateConnection({\n                    numberOfAllTransactions: getRandomInt(0, 1000),\n                    numberOfNewTransactions: getRandomInt(0, 150),\n                    numberOfRandomTransactionRequests: getRandomInt(0, 100),\n                    numberOfInvalidTransactions: getRandomInt(0, 10)\n                });\n            });\n        }\n\n        /////////////////////////////////// MOCK SPECIFICS ///////////////////////////////////\n\n    }, {\n        key: 'sendStats',\n        value: function sendStats() {\n            var _this3 = this;\n\n            var sockets = Array.from(this.sockets.values());\n\n            process.send({\n                isMaster: this.opts.isMaster,\n                peers: this.list ? this.list.all().map(function (p) {\n                    return p.data.port;\n                }) : [],\n                connections: {\n                    list: Array.from(this.sockets.keys()).filter(function (k) {\n                        return _this3.sockets.get(k).readyState === 1;\n                    }).map(function (peer) {\n                        return '' + peer.data.port;\n                    }),\n                    connecting: sockets.filter(function (s) {\n                        return s.readyState === 0;\n                    }).length,\n                    connected: sockets.filter(function (s) {\n                        return s.readyState === 1;\n                    }).length,\n                    closed: sockets.filter(function (s) {\n                        return s.readyState > 1;\n                    }).length\n                }\n            });\n        }\n    }]);\n\n    return Node;\n}(BaseNode);\n\nmodule.exports = {\n    DEFAULT_OPTIONS: DEFAULT_OPTIONS,\n    Node: Node\n};"
  },
  {
    "path": "dist/node/base.js",
    "content": "'use strict';\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nrequire('colors');\nvar terminal = require('./tools/terminal');\n\nvar DEFAULT_OPTIONS = {\n    silent: false,\n    logIdent: 'BASE',\n    logIdentWidth: 12\n};\n\n/**\n * Base class with generic functionality.\n * @class Base\n */\n\nvar Base = function () {\n    function Base(options) {\n        _classCallCheck(this, Base);\n\n        this.opts = _extends({}, DEFAULT_OPTIONS, options);\n    }\n\n    _createClass(Base, [{\n        key: 'log',\n        value: function log() {\n            if (!this.opts || !this.opts.silent || arguments[0] === '!!') {\n                var date = new Date();\n                var timeString = (date.toLocaleTimeString() + '.' + this.formatMilliseconds(date.getMilliseconds())).dim;\n                var space = this.opts.logIdent.length > this.opts.logIdentWidth ? '\\n' + ' '.repeat(this.opts.logIdentWidth) : ' '.repeat(this.opts.logIdentWidth - this.opts.logIdent.length);\n                var logIdent = ('' + this.opts.logIdent + space).dim.bold;\n\n                terminal.log.apply(terminal, [timeString + '\\t' + logIdent].concat(Array.prototype.slice.call(arguments)));\n            }\n        }\n    }, {\n        key: 'formatNode',\n        value: function formatNode(hostname, port) {\n            return (hostname + ':' + port).cyan;\n        }\n    }, {\n        key: 'formatMilliseconds',\n        value: function formatMilliseconds(milliseconds) {\n            var formatted = milliseconds / 1000;\n            formatted = formatted.toFixed(3);\n            formatted = formatted.toString();\n            return formatted.slice(2);\n        }\n    }, {\n        key: 'start',\n        value: function start() {}\n    }, {\n        key: 'end',\n        value: function end() {}\n    }]);\n\n    return Base;\n}();\n\nmodule.exports = {\n    DEFAULT_OPTIONS: DEFAULT_OPTIONS,\n    Base: Base\n};"
  },
  {
    "path": "dist/node/guard.js",
    "content": "'use strict';\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\nvar _require = require('./base'),\n    Base = _require.Base;\n\nvar _require2 = require('./tools/utils'),\n    getSecondsPassed = _require2.getSecondsPassed;\n\nvar DEFAULT_OPTIONS = {\n    beatInterval: 1,\n    throttleInterval: 2, // Minimal amount of beats to pass until a remote address is allowed again.\n    localNodes: false,\n    logIdent: 'GUARD'\n};\n\n/**\n * Simple throttling system for incoming connections.\n * @class Heart\n */\n\nvar Guard = function (_Base) {\n    _inherits(Guard, _Base);\n\n    function Guard(options) {\n        _classCallCheck(this, Guard);\n\n        var _this = _possibleConstructorReturn(this, (Guard.__proto__ || Object.getPrototypeOf(Guard)).call(this, _extends({}, DEFAULT_OPTIONS, options)));\n\n        _this.requests = {};\n        return _this;\n    }\n\n    _createClass(Guard, [{\n        key: 'isAllowed',\n        value: function isAllowed(address, port) {\n            var target = '' + (this.opts.localNodes ? port : address);\n            if (!this.requests[target]) {\n                this.requests[target] = new Date();\n                return true;\n            } else {\n                var allowed = getSecondsPassed(this.requests[target]) >= this.opts.beatInterval * this.opts.throttleInterval;\n                this.requests[target] = new Date();\n                return allowed;\n            }\n        }\n    }]);\n\n    return Guard;\n}(Base);\n\nmodule.exports = {\n    DEFAULT_OPTIONS: DEFAULT_OPTIONS,\n    Guard: Guard\n};"
  },
  {
    "path": "dist/node/heart.js",
    "content": "'use strict';\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\nvar _require = require('./base'),\n    Base = _require.Base;\n\nvar _require2 = require('./tools/utils'),\n    getSecondsPassed = _require2.getSecondsPassed,\n    getRandomInt = _require2.getRandomInt,\n    createIdentifier = _require2.createIdentifier;\n\nvar terminal = require('./tools/terminal');\n\nvar DEFAULT_OPTIONS = {\n    cycleInterval: 300,\n    epochInterval: 900,\n    beatInterval: 1,\n    autoStart: false,\n    logIdent: 'HEART',\n    onEpoch: function onEpoch(currentEpoch) {\n        return Promise.resolve(false);\n    },\n    onCycle: function onCycle(currentCycle) {\n        return Promise.resolve(false);\n    },\n    onTick: function onTick(currentCycle) {\n        return Promise.resolve(0);\n    }\n};\n\n/**\n * Manages epoch and cycle updates\n * @class Heart\n */\n\nvar Heart = function (_Base) {\n    _inherits(Heart, _Base);\n\n    function Heart(options) {\n        _classCallCheck(this, Heart);\n\n        var _this = _possibleConstructorReturn(this, (Heart.__proto__ || Object.getPrototypeOf(Heart)).call(this, _extends({}, DEFAULT_OPTIONS, options)));\n\n        _this.id = null;\n        _this.ticker = null;\n        _this.lastCycle = null;\n        _this.lastEpoch = null;\n        _this.personality = {};\n        _this.currentCycle = 0;\n        _this.currentEpoch = 0;\n        _this.startDate = null;\n        _this._tick = _this._tick.bind(_this);\n        _this.opts.autoStart && _this.start();\n        return _this;\n    }\n\n    _createClass(Heart, [{\n        key: 'start',\n        value: function start() {\n            this.startDate = new Date();\n            this.startNewEpoch();\n            this.lastCycle = new Date();\n            this.log('Cycle/epoch intervals:', this.opts.cycleInterval, this.opts.epochInterval);\n            terminal.settings({\n                epochInterval: this.opts.epochInterval,\n                cycleInterval: this.opts.cycleInterval,\n                startDate: this.startDate\n            });\n            this._tick();\n        }\n    }, {\n        key: 'end',\n        value: function end() {\n            this.ticker && clearTimeout(this.ticker);\n        }\n\n        /**\n         * Starts new epoch, resetting node identifiers and memorizing last epoch switch datetime.\n         */\n\n    }, {\n        key: 'startNewEpoch',\n        value: function startNewEpoch() {\n            this.setNewPersonality();\n            this.lastEpoch = new Date();\n            this.currentEpoch += 1;\n        }\n\n        /**\n         * Sets this heart's personality: ID, feature, etc.\n         */\n\n    }, {\n        key: 'setNewPersonality',\n        value: function setNewPersonality() {\n            var id = createIdentifier();\n            this.personality = {\n                id: id,\n                publicId: id.slice(0, 8),\n                feature: id[getRandomInt(0, id.length)]\n            };\n            this.log('new personality', this.personality.feature, this.personality.id);\n        }\n\n        /**\n         * Ticker that handles cycle and epoch changes.\n         * @private\n         */\n\n    }, {\n        key: '_tick',\n        value: function _tick() {\n            var _this2 = this;\n\n            this.opts.onTick(this.currentCycle).then(function () {\n                var passedSecondsEpoch = getSecondsPassed(_this2.lastEpoch);\n                var passedSecondsCycle = getSecondsPassed(_this2.lastCycle);\n                var pctEpoch = passedSecondsEpoch / _this2.opts.epochInterval;\n                var pctCycle = passedSecondsCycle / _this2.opts.cycleInterval;\n                terminal.beat({\n                    epoch: _this2.currentEpoch,\n                    cycle: _this2.currentCycle,\n                    startDate: _this2.startDate,\n                    pctEpoch: pctEpoch,\n                    pctCycle: pctCycle\n                });\n\n                if (passedSecondsCycle > _this2.opts.cycleInterval) {\n                    _this2.opts.onCycle(_this2.currentCycle).then(function (skipABeat) {\n                        if (!skipABeat) {\n                            _this2.lastCycle = new Date();\n                            _this2.currentCycle += 1;\n                            if (passedSecondsEpoch > _this2.opts.epochInterval) {\n                                _this2.opts.onEpoch(_this2.currentEpoch).then(function (skipAge) {\n                                    !skipAge && _this2.startNewEpoch();\n                                    _this2._setTicker();\n                                });\n                                return;\n                            }\n                        }\n                        _this2._setTicker();\n                    });\n                    return;\n                }\n                _this2._setTicker();\n            });\n        }\n\n        /**\n         * Sets the ticker for the next beat\n         * @private\n         */\n\n    }, {\n        key: '_setTicker',\n        value: function _setTicker() {\n            this.ticker && clearTimeout(this.ticker);\n            this.ticker = setTimeout(this._tick, this.opts.beatInterval * 1000);\n        }\n    }]);\n\n    return Heart;\n}(Base);\n\nmodule.exports = {\n    DEFAULT_OPTIONS: DEFAULT_OPTIONS,\n    Heart: Heart\n};"
  },
  {
    "path": "dist/node/index.js",
    "content": "'use strict';\n\nvar base = require('./base');\nvar heart = require('./heart');\nvar iri = require('./iri');\nvar node = require('./node');\nvar peer = require('./peer');\nvar peerList = require('./peer-list');\nvar utils = require('./tools/utils');\n\nmodule.exports = {\n    base: base,\n    heart: heart,\n    iri: iri,\n    node: node,\n    peer: peer,\n    peerList: peerList,\n    utils: utils\n};"
  },
  {
    "path": "dist/node/iri.js",
    "content": "'use strict';\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\nvar IOTA = require('iota.lib.js');\n\nvar _require = require('url'),\n    URL = _require.URL;\n\nvar tmp = require('tmp');\n\nvar _require2 = require('./base'),\n    Base = _require2.Base;\n\nvar _require3 = require('./tools/utils'),\n    getIP = _require3.getIP;\n\ntmp.setGracefulCleanup();\n\nvar DEFAULT_OPTIONS = {\n    hostname: 'localhost',\n    port: 14265,\n    TCPPort: 15600,\n    UDPPort: 14600,\n    logIdent: 'IRI',\n    onHealthCheck: function onHealthCheck(isHealthy, neighbors) {}\n};\n\n/**\n * Class responsible to RUN and communicate with local IRI instance\n * @class\n */\n\nvar IRI = function (_Base) {\n    _inherits(IRI, _Base);\n\n    function IRI(options) {\n        _classCallCheck(this, IRI);\n\n        var _this = _possibleConstructorReturn(this, (IRI.__proto__ || Object.getPrototypeOf(IRI)).call(this, _extends({}, DEFAULT_OPTIONS, options)));\n\n        _this.api = new IOTA({ host: 'http://' + _this.opts.hostname, port: _this.opts.port }).api;\n        _this.removeNeighbors = _this.removeNeighbors.bind(_this);\n        _this.addNeighbors = _this.addNeighbors.bind(_this);\n        _this.updateNeighbors = _this.updateNeighbors.bind(_this);\n        _this._tick = _this._tick.bind(_this);\n        _this._getIRIPeerURI = _this._getIRIPeerURI.bind(_this);\n        _this.ticker = null;\n        _this.isHealthy = false;\n        _this.iriStats = {};\n        _this.staticNeighbors = [];\n        return _this;\n    }\n\n    /**\n     * Starts the IRI process, returning self on success.\n     * @returns {Promise<IRI>}\n     */\n\n\n    _createClass(IRI, [{\n        key: 'start',\n        value: function start() {\n            var _this2 = this;\n\n            return new Promise(function (resolve) {\n                var getNodeInfo = function getNodeInfo() {\n                    return _this2.api.getNeighbors(function (error, neighbors) {\n                        if (!error) {\n                            var addresses = neighbors.map(function (n) {\n                                return new URL(n.connectionType + '://' + n.address).hostname;\n                            });\n                            Promise.all(addresses.map(getIP)).then(function (ips) {\n                                _this2._isStarted = true;\n                                _this2.isHealthy = true;\n                                _this2.staticNeighbors = ips.concat(addresses);\n                                _this2.log('Static neighbors: ' + addresses);\n                                // TODO: make ticker wait for result, like in the heart.\n                                _this2.ticker = setInterval(_this2._tick, 15000);\n                                _this2.getStats().then(function () {\n                                    return resolve(_this2);\n                                });\n                            });\n                        } else {\n                            _this2.log(('IRI not ready on ' + _this2.opts.hostname + ':' + _this2.opts.port + ', retrying...').yellow);\n                            setTimeout(getNodeInfo, 5000);\n                        }\n                    });\n                };\n                getNodeInfo();\n            });\n        }\n    }, {\n        key: 'end',\n        value: function end() {\n            this.isHealthy = false;\n            this._isStarted = false;\n            this.staticNeighbors = [];\n            this.ticker && clearTimeout(this.ticker);\n            this.ticker = null;\n        }\n\n        /**\n         * Returns whether the process has been started.\n         * @returns {boolean}\n         */\n\n    }, {\n        key: 'isStarted',\n        value: function isStarted() {\n            return this._isStarted;\n        }\n\n        /**\n         * Returns whether the IRI process is running and can be communicated with.\n         * @returns {boolean}\n         */\n\n    }, {\n        key: 'isAvailable',\n        value: function isAvailable() {\n            return this.isStarted() && this.isHealthy;\n        }\n\n        /**\n         * Returns whether a peer's IP or hostname is added as static neighbor in IRI.\n         * @param {Peer} peer\n         * @returns {boolean}\n         */\n\n    }, {\n        key: 'isStaticNeighbor',\n        value: function isStaticNeighbor(peer) {\n            return !!this.staticNeighbors.filter(function (n) {\n                return n === peer.data.ip || n === peer.data.hostname;\n            }).length;\n        }\n\n        /**\n         * Removes a list of neighbors from IRI, except static neighbors. Returns list of removed peers.\n         * @param {Peer[]} peers\n         * @returns {Promise<Peer[]>}\n         */\n\n    }, {\n        key: 'removeNeighbors',\n        value: function removeNeighbors(peers) {\n            var _this3 = this;\n\n            if (!this.isAvailable()) {\n                return Promise.reject();\n            }\n\n            var myPeers = peers.filter(function (peer) {\n                if (_this3.isStaticNeighbor(peer)) {\n                    _this3.log(('WARNING: trying to remove a static neighbor. Skipping: ' + peer.data.hostname).yellow);\n                    return false;\n                }\n                return true;\n            });\n\n            if (!peers.length) {\n                return Promise.resolve([]);\n            }\n\n            var uris = myPeers.map(this._getIRIPeerURI);\n            return new Promise(function (resolve, reject) {\n                _this3.api.removeNeighbors(uris, function (err) {\n                    if (err) {\n                        reject(err);\n                        return;\n                    }\n                    _this3.log('Neighbors removed (if there were any):'.red, uris.join(', '));\n                    resolve(peers);\n                });\n            });\n        }\n\n        /**\n         * Adds a list of peers to IRI.\n         * @param {Peer[]} peers\n         * @returns {Promise<Peer[]>}\n         */\n\n    }, {\n        key: 'addNeighbors',\n        value: function addNeighbors(peers) {\n            var _this4 = this;\n\n            if (!this.isAvailable()) {\n                return Promise.reject();\n            }\n\n            var uris = peers.map(this._getIRIPeerURI);\n\n            return new Promise(function (resolve, reject) {\n                _this4.api.addNeighbors(uris, function (error) {\n                    if (error) {\n                        reject(error);\n                        return;\n                    }\n                    _this4.log('Neighbors added:'.green, uris.join(', '));\n                    resolve(peers);\n                });\n            });\n        }\n\n        /**\n         * Cleans up any orphans from the IRI\n         * @param {Peer[]} peers\n         * @returns {Promise<URL[]>}\n         */\n\n    }, {\n        key: 'cleanupNeighbors',\n        value: function cleanupNeighbors(peers) {\n            var _this5 = this;\n\n            if (!this.isAvailable()) {\n                return Promise.reject();\n            }\n            return new Promise(function (resolve) {\n                _this5.api.getNeighbors(function (error, neighbors) {\n                    if (error) {\n                        return resolve();\n                    }\n                    Promise.all(neighbors.map(function (n) {\n                        var url = new URL(n.connectionType + '://' + n.address);\n                        return getIP(url.hostname).then(function (ip) {\n                            url.ip = ip || 'none';\n                            return url;\n                        });\n                    })).then(function (urls) {\n                        var toRemove = urls.filter(function (url) {\n                            return !_this5.staticNeighbors.includes(url.hostname) && !_this5.staticNeighbors.includes(url.ip) && peers.filter(function (p) {\n                                return p.data.hostname === url.hostname || p.data.ip === url.hostname || p.data.hostname === url.ip || p.data.ip === url.ip;\n                            }).length === 0;\n                        });\n                        if (!toRemove.length) {\n                            return resolve(toRemove);\n                        }\n                        _this5.api.removeNeighbors(toRemove, function (err) {\n                            if (err) {\n                                reject(err);\n                                return;\n                            }\n                            _this5.log('Removed orphans:'.red, toRemove.map(function (url) {\n                                return url.hostname;\n                            }));\n                            resolve(toRemove);\n                        });\n                    });\n                });\n            });\n        }\n\n        /**\n         * Updates the list of neighbors at the IRI backend. Removes all neighbors, replacing them with\n         * the newly provided neighbors.\n         * @param {Peer[]} peers\n         * @returns {Promise<Peer[]>}\n         */\n\n    }, {\n        key: 'updateNeighbors',\n        value: function updateNeighbors(peers) {\n            var _this6 = this;\n\n            if (!this.isAvailable()) {\n                return Promise.reject();\n            }\n\n            if (!peers || !peers.length) {\n                return Promise.resolve([]);\n            }\n\n            return new Promise(function (resolve, reject) {\n                var addNeighbors = function addNeighbors() {\n                    _this6.addNeighbors(peers).then(resolve).catch(reject);\n                };\n\n                _this6.api.getNeighbors(function (error, neighbors) {\n                    if (error) {\n                        reject(error);\n                        return;\n                    }\n                    Array.isArray(neighbors) && neighbors.length ? _this6.api.removeNeighbors(neighbors.map(function (n) {\n                        return n.connectionType + '://' + n.address;\n                    }), addNeighbors) : addNeighbors();\n                });\n            });\n        }\n\n        /**\n         * Removes all IRI neighbors, except static neighbors.\n         * @returns {Promise}\n         */\n\n    }, {\n        key: 'removeAllNeighbors',\n        value: function removeAllNeighbors() {\n            var _this7 = this;\n\n            if (!this.isAvailable()) {\n                return Promise.reject();\n            }\n\n            return new Promise(function (resolve) {\n                _this7.api.getNeighbors(function (error, neighbors) {\n                    if (error) {\n                        return resolve();\n                    }\n                    if (Array.isArray(neighbors) && neighbors.length) {\n                        // FIXME: This is broken. staticNeighbors is just a resolved IP. n.address includes port and can be a hostname.\n                        // Hence, the filter will always be true.\n                        var toRemove = neighbors.filter(function (n) {\n                            return !_this7.staticNeighbors.includes(n.address);\n                        });\n                        return _this7.api.removeNeighbors(toRemove.map(function (n) {\n                            return n.connectionType + '://' + n.address;\n                        }), resolve);\n                    }\n                    resolve();\n                });\n            });\n        }\n\n        /**\n         * Returns IRI node info\n         * @returns {Promise<object>}\n         */\n\n    }, {\n        key: 'getStats',\n        value: function getStats() {\n            var _this8 = this;\n\n            return new Promise(function (resolve, reject) {\n                _this8.api.getNodeInfo(function (error, data) {\n                    if (error) {\n                        return reject();\n                    }\n                    _this8.iriStats = data;\n                    resolve(data);\n                });\n            });\n        }\n\n        /**\n         * Checks if the IRI instance is healthy, and its list of neighbors. Calls back the result to onHealthCheck.\n         * @private\n         */\n\n    }, {\n        key: '_tick',\n        value: function _tick() {\n            var _this9 = this;\n\n            var onHealthCheck = this.opts.onHealthCheck;\n\n            var onError = function onError() {\n                _this9.isHealthy = false;\n                onHealthCheck(false);\n            };\n            this.getStats().then(function () {\n                _this9.api.getNeighbors(function (error, neighbors) {\n                    if (error) {\n                        return onError();\n                    }\n                    _this9.isHealthy = true;\n                    // TODO: if the address is IPV6, could that pose a problem?\n                    onHealthCheck(true, neighbors.map(function (n) {\n                        return {\n                            address: new URL(n.connectionType + '://' + n.address).hostname,\n                            numberOfRandomTransactionRequests: n.numberOfRandomTransactionRequests,\n                            numberOfAllTransactions: n.numberOfAllTransactions,\n                            numberOfNewTransactions: n.numberOfNewTransactions,\n                            numberOfInvalidTransactions: n.numberOfInvalidTransactions\n                        };\n                    }));\n                });\n            }).catch(onError);\n        }\n\n        /**\n         * Returns URI for IRI depending on the protocol.\n         * @param {Peer} peer\n         * @returns {string}\n         * @private\n         */\n\n    }, {\n        key: '_getIRIPeerURI',\n        value: function _getIRIPeerURI(peer) {\n            return peer.data.IRIProtocol === 'tcp' ? peer.getTCPURI() : peer.getUDPURI();\n        }\n    }]);\n\n    return IRI;\n}(Base);\n\nmodule.exports = {\n    DEFAULT_OPTIONS: DEFAULT_OPTIONS,\n    IRI: IRI\n};"
  },
  {
    "path": "dist/node/node.js",
    "content": "'use strict';\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\nvar WebSocket = require('ws');\nvar ip = require('ip');\nvar pip = require('external-ip')();\nvar weighted = require('weighted');\nvar terminal = require('./tools/terminal');\n\nvar _require = require('./base'),\n    Base = _require.Base;\n\nvar _require2 = require('./heart'),\n    Heart = _require2.Heart;\n\nvar _require3 = require('./guard'),\n    Guard = _require3.Guard;\n\nvar _require4 = require('./iri'),\n    IRI = _require4.IRI,\n    DEFAULT_IRI_OPTIONS = _require4.DEFAULT_OPTIONS;\n\nvar _require5 = require('./peer-list'),\n    PeerList = _require5.PeerList,\n    DEFAULT_LIST_OPTIONS = _require5.DEFAULT_OPTIONS;\n\nvar _require6 = require('./tools/utils'),\n    getPeerIdentifier = _require6.getPeerIdentifier,\n    getRandomInt = _require6.getRandomInt,\n    getSecondsPassed = _require6.getSecondsPassed,\n    getVersion = _require6.getVersion,\n    isSameMajorVersion = _require6.isSameMajorVersion,\n    getIP = _require6.getIP,\n    createIdentifier = _require6.createIdentifier;\n\nprocess.on('unhandledRejection', function (reason, p) {\n    console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);\n});\n\nvar DEFAULT_OPTIONS = {\n    name: 'Deviota Nelson',\n    cycleInterval: 60,\n    epochInterval: 1200,\n    beatInterval: 10,\n    dataPath: DEFAULT_LIST_OPTIONS.dataPath,\n    port: 16600,\n    IRIHostname: DEFAULT_IRI_OPTIONS.hostname,\n    IRIPort: DEFAULT_IRI_OPTIONS.port,\n    IRIProtocol: 'any',\n    TCPPort: DEFAULT_IRI_OPTIONS.TCPPort,\n    UDPPort: DEFAULT_IRI_OPTIONS.UDPPort,\n    weightDeflation: 0.95,\n    incomingMax: 6,\n    outgoingMax: 5,\n    maxShareableNodes: 6,\n    localNodes: false,\n    isMaster: false,\n    temporary: false,\n    autoStart: false,\n    logIdent: 'NODE',\n    neighbors: [],\n    lazyLimit: 300, // Time, after which a peer is considered lazy, if no new TXs received\n    lazyTimesLimit: 3, // starts to penalize peer's quality if connected so many times without new TXs\n    onReady: function onReady(node) {},\n    onPeerConnected: function onPeerConnected(peer) {},\n    onPeerRemoved: function onPeerRemoved(peer) {}\n};\n\n// TODO: add node tests. Need to mock away IRI for this.\n\nvar Node = function (_Base) {\n    _inherits(Node, _Base);\n\n    function Node(options) {\n        _classCallCheck(this, Node);\n\n        var _this = _possibleConstructorReturn(this, (Node.__proto__ || Object.getPrototypeOf(Node)).call(this, _extends({}, DEFAULT_OPTIONS, options)));\n\n        _this.opts.logIdent = _this.opts.port + '::NODE';\n\n        _this._onCycle = _this._onCycle.bind(_this);\n        _this._onEpoch = _this._onEpoch.bind(_this);\n        _this._onTick = _this._onTick.bind(_this);\n        _this._onIRIHealth = _this._onIRIHealth.bind(_this);\n        _this._removeNeighbor = _this._removeNeighbor.bind(_this);\n        _this._removeNeighbors = _this._removeNeighbors.bind(_this);\n        _this._addNeighbor = _this._addNeighbor.bind(_this);\n        _this._addNeighbors = _this._addNeighbors.bind(_this);\n        _this.connectPeer = _this.connectPeer.bind(_this);\n        _this.reconnectPeers = _this.reconnectPeers.bind(_this);\n        _this.end = _this.end.bind(_this);\n\n        _this._ready = false;\n        _this.sockets = new Map();\n\n        _this.opts.autoStart && _this.start();\n\n        // Tries to fix the issue #45 https://github.com/SemkoDev/nelson.cli/issues/45\n        // Reasoning: https://github.com/request/request/issues/2161#issuecomment-313375694\n        // Also, cleans up nelson before crashing from the sky.\n        process.on('uncaughtException', function (err) {\n            if (err.code !== 'ECONNRESET') {\n                _this.end().then(function () {\n                    throw err;\n                });\n            }\n        });\n        return _this;\n    }\n\n    /**\n     * Starts the node server, getting public IP, IRI interface, Peer List and Heart.\n     */\n\n\n    _createClass(Node, [{\n        key: 'start',\n        value: function start() {\n            var _this2 = this;\n\n            var _opts = this.opts,\n                cycleInterval = _opts.cycleInterval,\n                epochInterval = _opts.epochInterval,\n                beatInterval = _opts.beatInterval,\n                silent = _opts.silent,\n                localNodes = _opts.localNodes;\n\n            this.guard = new Guard({ beatInterval: beatInterval, silent: silent, localNodes: localNodes });\n\n            return this._setPublicIP().then(function () {\n                return _this2._getIRI().then(function (iri) {\n                    if (!iri) {\n                        throw new Error('IRI could not be started');\n                    }\n\n                    if (!iri.staticNeighbors.length && _this2.opts.outgoingMax < DEFAULT_OPTIONS.outgoingMax) {\n                        _this2.log('WARNING: you have no static neighbors and outboundMax (' + _this2.opts.outgoingMax + ') is set below the advised limit (' + DEFAULT_OPTIONS.outgoingMax + ')!');\n                    }\n\n                    if (_this2.opts.incomingMax < DEFAULT_OPTIONS.incomingMax) {\n                        _this2.log('WARNING: incomingMax (' + _this2.opts.incomingMax + ') is set below the advised limit (' + DEFAULT_OPTIONS.incomingMax + ')!');\n                    }\n\n                    if (_this2.opts.incomingMax <= DEFAULT_OPTIONS.outgoingMax) {\n                        _this2.log('WARNING: incomingMax (' + _this2.opts.incomingMax + ') is set below outgoingMax (' + DEFAULT_OPTIONS.outgoingMax + ')!');\n                    }\n\n                    return _this2._getList().then(function () {\n\n                        _this2._createServer();\n\n                        _this2.heart = new Heart({\n                            silent: silent,\n                            cycleInterval: cycleInterval,\n                            epochInterval: epochInterval,\n                            beatInterval: beatInterval,\n                            logIdent: _this2.opts.port + '::HEART',\n                            onCycle: _this2._onCycle,\n                            onTick: _this2._onTick,\n                            onEpoch: _this2._onEpoch\n                        });\n\n                        _this2._ready = true;\n                        _this2.opts.onReady(_this2);\n                        _this2.heart.start();\n\n                        return _this2;\n                    }).catch(function (err) {\n                        throw err;\n                    });\n                }).catch(function (err) {\n                    throw err;\n                });\n            });\n        }\n\n        /**\n         * Ends the node, closing HTTP server and IRI backend.\n         * @returns {Promise.<boolean>}\n         */\n\n    }, {\n        key: 'end',\n        value: function end() {\n            var _this3 = this;\n\n            this.log('terminating...');\n\n            this.heart && this.heart.end();\n            this._ready = false;\n\n            var closeServer = function closeServer() {\n                return new Promise(function (resolve) {\n                    if (_this3.server) {\n                        _this3.server.close();\n                    }\n                    return _this3._removeNeighbors(Array.from(_this3.sockets.keys())).then(function () {\n                        _this3.sockets = new Map();\n                        resolve(true);\n                    });\n                });\n            };\n\n            return closeServer().then(function () {\n                return _this3.iri ? _this3.iri.end() : true;\n            });\n        }\n\n        /**\n         * Sets a new peer list and returns a list of loaded peers.\n         * @returns {Promise.<Peer[]>}\n         * @private\n         */\n\n    }, {\n        key: '_getList',\n        value: function _getList() {\n            var _this4 = this;\n\n            var _opts2 = this.opts,\n                localNodes = _opts2.localNodes,\n                temporary = _opts2.temporary,\n                silent = _opts2.silent,\n                neighbors = _opts2.neighbors,\n                dataPath = _opts2.dataPath,\n                isMaster = _opts2.isMaster,\n                lazyLimit = _opts2.lazyLimit,\n                lazyTimesLimit = _opts2.lazyTimesLimit;\n\n            this.list = new PeerList({\n                multiPort: localNodes,\n                temporary: temporary,\n                silent: silent,\n                dataPath: dataPath,\n                isMaster: isMaster,\n                lazyLimit: lazyLimit,\n                lazyTimesLimit: lazyTimesLimit,\n                logIdent: this.opts.port + '::LIST'\n            });\n\n            return this.list.load(neighbors.filter(function (n) {\n                var tokens = n.split('/');\n                return !_this4.isMyself(tokens[0], tokens[1]);\n            }));\n        }\n\n        /**\n         * Sets and returns an IRI instance\n         * @returns {Promise.<IRI>}\n         * @private\n         */\n\n    }, {\n        key: '_getIRI',\n        value: function _getIRI() {\n            var _this5 = this;\n\n            var _opts3 = this.opts,\n                IRIHostname = _opts3.IRIHostname,\n                IRIPort = _opts3.IRIPort,\n                silent = _opts3.silent;\n\n\n            return new IRI({\n                logIdent: this.opts.port + '::IRI',\n                hostname: IRIHostname,\n                port: IRIPort,\n                onHealthCheck: this._onIRIHealth,\n                silent: silent\n            }).start().then(function (iri) {\n                _this5.iri = iri;\n                return iri;\n            });\n        }\n\n        /**\n         * Tries to get the public IPs of this node.\n         * @private\n         * @returns {Promise}\n         */\n\n    }, {\n        key: '_setPublicIP',\n        value: function _setPublicIP() {\n            var _this6 = this;\n\n            if (this.opts.localNodes) {\n                return Promise.resolve(0);\n            }\n            return new Promise(function (resolve) {\n                pip(function (err, ip) {\n                    if (!err) {\n                        _this6.ipv4 = ip;\n                        resolve(0);\n                    }\n                });\n            });\n        }\n\n        /**\n         * Creates HTTP server for Nelson\n         * @private\n         */\n\n    }, {\n        key: '_createServer',\n        value: function _createServer() {\n            var _this7 = this;\n\n            this.server = new WebSocket.Server({ port: this.opts.port, verifyClient: function verifyClient(info, cb) {\n                    var req = info.req;\n\n                    var deny = function deny() {\n                        return cb(false, 401);\n                    };\n                    var accept = function accept() {\n                        return cb(true);\n                    };\n                    _this7._canConnect(req).then(accept).catch(deny);\n                } });\n\n            this.server.on('connection', function (ws, req) {\n                _this7.log('incoming connection established'.green, req.connection.remoteAddress);\n                var address = req.connection.remoteAddress;\n\n                var _getHeaderIdentifiers2 = _this7._getHeaderIdentifiers(req.headers),\n                    port = _getHeaderIdentifiers2.port,\n                    TCPPort = _getHeaderIdentifiers2.TCPPort,\n                    UDPPort = _getHeaderIdentifiers2.UDPPort,\n                    remoteKey = _getHeaderIdentifiers2.remoteKey,\n                    name = _getHeaderIdentifiers2.name,\n                    protocol = _getHeaderIdentifiers2.protocol;\n\n                var IRIProtocol = _this7._negotiateProtocol(protocol);\n\n                _this7.list.add({\n                    hostname: address,\n                    port: port,\n                    TCPPort: TCPPort,\n                    UDPPort: UDPPort,\n                    remoteKey: remoteKey,\n                    name: name,\n                    IRIProtocol: IRIProtocol\n                }).then(function (peer) {\n                    _this7._bindWebSocket(ws, peer, true);\n                }).catch(function (e) {\n                    _this7.log('Error binding/adding'.red, address, port, e);\n                    _this7.sockets.delete(Array.from(_this7.sockets.keys()).find(function (p) {\n                        return _this7.sockets.get(p) === ws;\n                    }));\n                    ws.close();\n                    ws.terminate();\n                });\n            });\n\n            this.server.on('headers', function (headers) {\n                var myHeaders = _this7._getHeaders();\n                Object.keys(myHeaders).forEach(function (key) {\n                    return headers.push(key + ': ' + myHeaders[key]);\n                });\n            });\n            this.server.on('error', function (err) {\n                // basically, do nothing. Most probably a ECONNRESET error.\n                // The peer will be cleaned up on next tick.\n            });\n            this.log('server created...');\n        }\n\n        /**\n         * Resolves promise if the client is allowed to connect, otherwise rejection.\n         * @param {object} req\n         * @returns {Promise}\n         * @private\n         */\n\n    }, {\n        key: '_canConnect',\n        value: function _canConnect(req) {\n            var _this8 = this;\n\n            var address = req.connection.remoteAddress;\n\n            var headers = this._getHeaderIdentifiers(req.headers);\n\n            var _ref = headers || {},\n                port = _ref.port,\n                nelsonID = _ref.nelsonID,\n                version = _ref.version,\n                remoteKey = _ref.remoteKey,\n                protocol = _ref.protocol;\n\n            var wrongRequest = !headers;\n\n            return new Promise(function (resolve, reject) {\n                if (!_this8._ready || !_this8.guard || !_this8.guard.isAllowed(address, port)) {\n                    return reject();\n                }\n\n                if (wrongRequest || !isSameMajorVersion(version)) {\n                    _this8.log('Wrong request or other Nelson version', address, port, version, nelsonID, req.headers);\n                    return reject();\n                }\n                if (!_this8.iri || !_this8.iri.isHealthy) {\n                    _this8.log('IRI down, denying connections meanwhile', address, port, nelsonID);\n                    return reject();\n                }\n                if (_this8.isMyself(address, port, nelsonID)) {\n                    return reject();\n                }\n                _this8.list.findByRemoteKeyOrAddress(remoteKey, address, port).then(function (peers) {\n\n                    if (peers.length && _this8.sockets.get(peers[0])) {\n                        _this8.log('Peer already connected', address, port);\n                        return reject();\n                    }\n\n                    if (peers.length && _this8.iri.isStaticNeighbor(peers[0])) {\n                        _this8.log('Peer is already a static neighbor', address, port);\n                        return reject();\n                    }\n\n                    // Deny too frequent connections from the same peer.\n                    if (peers.length && _this8.isSaturationReached() && peers[0].data.dateLastConnected && getSecondsPassed(peers[0].data.dateLastConnected) < _this8.opts.epochInterval * 2) {\n                        return reject();\n                    }\n\n                    // Incompatible protocols\n                    if (peers.length[0] && !_this8._negotiateProtocol(protocol)) {\n                        _this8.log(('Couldn\\'t negotiate protocol with ' + peers[0].data.hostname + ': my ' + _this8.opts.IRIProtocol + ' vs remote ' + protocol).yellow);\n                        return reject();\n                    }\n\n                    var topCount = parseInt(Math.sqrt(_this8.list.all().length) * 2);\n                    var topPeers = _this8.list.getWeighted(300).sort(function (a, b) {\n                        return b[1] - a[1];\n                    }).map(function (p) {\n                        return p[0];\n                    }).slice(0, topCount);\n                    var isTop = false;\n\n                    peers.forEach(function (p) {\n                        if (topPeers.includes(p)) {\n                            isTop = true;\n                        }\n                    });\n\n                    // The usual way, accept based on personality.\n                    var normalPath = function normalPath() {\n                        if (_this8._getIncomingSlotsCount() >= _this8.opts.incomingMax) {\n                            reject();\n                        }\n\n                        // TODO: additional protection measure: make the client solve a computational riddle!\n\n                        _this8.isAllowed(remoteKey, address, port).then(function (allowed) {\n                            return allowed ? resolve() : reject();\n                        });\n                    };\n\n                    // Accept old, established nodes.\n                    if (isTop) {\n                        if (_this8._getIncomingSlotsCount() >= _this8.opts.incomingMax) {\n                            _this8._dropRandomNeighbors(1, true).then(resolve);\n                        } else {\n                            resolve();\n                        }\n                    }\n                    // Accept new nodes more easily.\n                    else if (!peers.length || getSecondsPassed(peers[0].data.dateCreated) <= _this8.opts.epochInterval * 10) {\n                            if (_this8._getIncomingSlotsCount() >= _this8.opts.incomingMax) {\n                                var candidates = Array.from(_this8.sockets.keys()).filter(function (p) {\n                                    return getSecondsPassed(p.data.dateCreated) <= _this8.opts.epochInterval * 20;\n                                });\n                                if (candidates.length) {\n                                    _this8._dropRandomNeighbors(1, true, candidates).then(resolve);\n                                } else {\n                                    normalPath();\n                                }\n                            } else {\n                                resolve();\n                            }\n                        } else {\n                            normalPath();\n                        }\n                });\n            });\n        }\n\n        /**\n         * Binds the websocket to the peer and adds callbacks.\n         * @param {WebSocket} ws\n         * @param {Peer} peer\n         * @param {boolean} asServer\n         * @private\n         */\n\n    }, {\n        key: '_bindWebSocket',\n        value: function _bindWebSocket(ws, peer) {\n            var _this9 = this;\n\n            var asServer = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;\n\n            var removeNeighbor = function removeNeighbor(e) {\n                if (!_this9._ready || !ws || ws.removingNow) {\n                    return;\n                }\n                ws.removingNow = true;\n                _this9._removeNeighbor(peer).then(function () {\n                    _this9.log('connection closed'.red, _this9.formatNode(peer.data.hostname, peer.data.port), '(' + e + ')');\n                });\n            };\n\n            var onConnected = function onConnected() {\n                if (!_this9._ready) {\n                    return;\n                }\n                _this9.log('connection established'.green, _this9.formatNode(peer.data.hostname, peer.data.port));\n                _this9._sendNeighbors(ws);\n                return peer.markConnected().then(function () {\n                    return _this9._ready && _this9.opts.onPeerConnected(peer);\n                });\n            };\n\n            ws.isAlive = true;\n            ws.incoming = asServer;\n            this.sockets.set(peer, ws);\n\n            ws.on('message', function (data) {\n                return _this9._addNeighbors(data, ws.incoming ? 0 : peer.data.weight);\n            });\n            ws.on('close', function () {\n                return removeNeighbor('socket closed');\n            });\n            ws.on('error', function () {\n                return removeNeighbor('remotely dropped');\n            });\n            ws.on('pong', function () {\n                ws.isAlive = true;\n            });\n\n            if (asServer) {\n                onConnected().then(function () {\n                    return _this9._ready && _this9.iri.addNeighbors([peer]);\n                });\n            } else {\n                ws.on('upgrade', function (res) {\n                    // Check for valid headers\n                    var head = _this9._getHeaderIdentifiers(res.headers);\n                    if (!head) {\n                        _this9.log('!!', 'wrong headers received', head);\n                        return removeNeighbor();\n                    }\n                    var port = head.port,\n                        nelsonID = head.nelsonID,\n                        TCPPort = head.TCPPort,\n                        UDPPort = head.UDPPort,\n                        remoteKey = head.remoteKey,\n                        name = head.name,\n                        protocol = head.protocol;\n\n                    var IRIProtocol = _this9._negotiateProtocol(protocol);\n                    _this9.list.update(peer, { port: port, nelsonID: nelsonID, TCPPort: TCPPort, UDPPort: UDPPort, remoteKey: remoteKey, name: name, IRIProtocol: IRIProtocol }).then(function () {\n                        if (IRIProtocol) {\n                            _this9._ready && _this9.iri.addNeighbors([peer]);\n                        } else {\n                            _this9.log(('Couldn\\'t negotiate protocol with ' + peer.data.hostname + ': my ' + _this9.opts.IRIProtocol + ' vs remote ' + IRIProtocol).yellow);\n                            removeNeighbor();\n                        }\n                    });\n                });\n                ws.on('open', onConnected);\n            }\n        }\n\n        /**\n         * Parses the headers passed between nelson instances\n         * @param {object} headers\n         * @returns {object}\n         * @private\n         */\n\n    }, {\n        key: '_getHeaderIdentifiers',\n        value: function _getHeaderIdentifiers(headers) {\n            var version = headers['nelson-version'];\n            var port = headers['nelson-port'];\n            var nelsonID = headers['nelson-id'];\n            var TCPPort = headers['nelson-tcp'];\n            var UDPPort = headers['nelson-udp'];\n            var remoteKey = headers['nelson-key'];\n            var name = headers['nelson-name'];\n            var protocol = headers['nelson-protocol'] || 'udp';\n            if (!version || !port || !nelsonID || !TCPPort || !UDPPort) {\n                return null;\n            }\n            return { version: version, port: port, nelsonID: nelsonID, TCPPort: TCPPort, UDPPort: UDPPort, remoteKey: remoteKey, name: name, protocol: protocol };\n        }\n\n        /**\n         * Sends list of neighbors through the given socket.\n         * @param {WebSocket} ws\n         * @private\n         */\n\n    }, {\n        key: '_sendNeighbors',\n        value: function _sendNeighbors(ws) {\n            ws.send(JSON.stringify(this.getPeers().map(function (p) {\n                return p[0].getHostname().replace('/0/', '/' + p[1] + '/');\n            })));\n        }\n\n        /**\n         * Negotiate protocol to be used between the peers.\n         * If null is returned, the connection cannot be established as there is no consensus.\n         * @param {string} protocol preferred by remote\n         * @param {string} key key for remote\n         * @param {string} remoteKey for this node\n         * @returns {string|null}\n         * @private\n         */\n\n    }, {\n        key: '_negotiateProtocol',\n        value: function _negotiateProtocol(protocol) {\n            if (protocol === 'any') {\n                switch (this.opts.IRIProtocol) {\n                    case 'tcp':\n                    case 'prefertcp':\n                        return 'tcp';\n                    case 'udp':\n                    case 'preferudp':\n                    case 'any':\n                    default:\n                        return 'udp';\n                }\n            } else if (protocol === 'tcp') {\n                switch (this.opts.IRIProtocol) {\n                    case 'any':\n                    case 'tcp':\n                    case 'prefertcp':\n                    case 'preferudp':\n                        return 'tcp';\n                    case 'udp':\n                    default:\n                        return null;\n                }\n            } else if (protocol === 'udp') {\n                switch (this.opts.IRIProtocol) {\n                    case 'any':\n                    case 'udp':\n                    case 'prefertcp':\n                    case 'preferudp':\n                        return 'udp';\n                    case 'tcp':\n                    default:\n                        return null;\n                }\n            } else if (protocol === 'prefertcp') {\n                switch (this.opts.IRIProtocol) {\n                    case 'any':\n                    case 'tcp':\n                    case 'prefertcp':\n                        return 'tcp';\n                    case 'preferudp':\n                    case 'udp':\n                    default:\n                        return 'udp';\n                }\n            } else if (protocol === 'preferudp') {\n                switch (this.opts.IRIProtocol) {\n                    case 'any':\n                    case 'udp':\n                    case 'preferudp':\n                    case 'prefertcp':\n                        return 'udp';\n                    case 'tcp':\n                    default:\n                        return 'tcp';\n                }\n            }\n        }\n\n        /**\n         * Adds a neighbor to known neighbors list.\n         * @param {string} neighbor\n         * @param {number} weight of the neighbor to assign\n         * @returns {Promise}\n         * @private\n         */\n\n    }, {\n        key: '_addNeighbor',\n        value: function _addNeighbor(neighbor, weight) {\n            // this.log('adding neighbor', neighbor);\n            var tokens = neighbor.split('/');\n            if (!isFinite(tokens[1]) || !isFinite(tokens[2]) || !isFinite(tokens[3])) {\n                return Promise.resolve(null);\n            }\n            return this.isMyself(tokens[0], tokens[1]) ? Promise.resolve(null) : this.list.add({\n                hostname: tokens[0],\n                port: tokens[1],\n                TCPPort: tokens[2],\n                UDPPort: tokens[3],\n                peerWeight: weight,\n                weight: weight * parseFloat(tokens[4] || 0) * this.opts.weightDeflation,\n                IRIProtocol: tokens[5] || 'udp'\n            });\n        }\n\n        /**\n         * Parses raw data from peer's response and adds the provided neighbors.\n         * @param {string} data raw from peer's response\n         * @param {number} weight to assign to the parsed neighbors.\n         * @returns {Promise}\n         * @private\n         */\n\n    }, {\n        key: '_addNeighbors',\n        value: function _addNeighbors(data, weight) {\n            var _this10 = this;\n\n            // this.log('add neighbors', data);\n            return new Promise(function (resolve, reject) {\n                try {\n                    Promise.all(JSON.parse(data).slice(0, _this10.opts.maxShareableNodes).map(function (neighbor) {\n                        return _this10._addNeighbor(neighbor, weight);\n                    })).then(resolve);\n                } catch (e) {\n                    reject(e);\n                }\n            });\n        }\n\n        /**\n         * Returns Nelson headers for request/response purposes\n         * @param {string} key of the peer\n         * @returns {Object}\n         * @private\n         */\n\n    }, {\n        key: '_getHeaders',\n        value: function _getHeaders() {\n            var key = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';\n\n            return {\n                'Content-Type': 'application/json',\n                'Nelson-Version': getVersion(),\n                'Nelson-Port': '' + this.opts.port,\n                'Nelson-ID': this.heart.personality.publicId,\n                'Nelson-TCP': this.opts.TCPPort,\n                'Nelson-UDP': this.opts.UDPPort,\n                'Nelson-Key': key,\n                'Nelson-Name': this.opts.name,\n                'Nelson-Protocol': this.opts.IRIProtocol\n            };\n        }\n\n        /**\n         * Returns amount of incoming connections\n         * @returns {Number}\n         * @private\n         */\n\n    }, {\n        key: '_getIncomingSlotsCount',\n        value: function _getIncomingSlotsCount() {\n            var arr = Array.from(this.sockets.values()).filter(function (ws) {\n                return ws.readyState < 2;\n            });\n            return arr.filter(function (ws) {\n                return ws.incoming;\n            }).length;\n        }\n\n        /**\n         * Returns amount of outgoing connections\n         * @returns {Number}\n         * @private\n         */\n\n    }, {\n        key: '_getOutgoingSlotsCount',\n        value: function _getOutgoingSlotsCount() {\n            var arr = Array.from(this.sockets.values()).filter(function (ws) {\n                return ws.readyState < 2;\n            });\n            return arr.filter(function (ws) {\n                return !ws.incoming;\n            }).length;\n        }\n\n        /**\n         * Disconnects a peer.\n         * @param {Peer} peer\n         * @returns {Promise<Peer>}\n         * @private\n         */\n\n    }, {\n        key: '_removeNeighbor',\n        value: function _removeNeighbor(peer) {\n            if (!this._ready || !this.sockets.get(peer)) {\n                return Promise.resolve([]);\n            }\n            // this.log('removing neighbor', this.formatNode(peer.data.hostname, peer.data.port));\n            return this._removeNeighbors([peer]);\n        }\n\n        /**\n         * Disconnects several peers.\n         * @param {Peer[]} peers\n         * @returns {Promise<Peer[]>}\n         * @private\n         */\n\n    }, {\n        key: '_removeNeighbors',\n        value: function _removeNeighbors(peers) {\n            var _this11 = this;\n\n            // this.log('removing neighbors');\n\n            var doRemove = function doRemove() {\n                return Promise.all(peers.map(function (peer) {\n                    return new Promise(function (resolve) {\n                        var ws = _this11.sockets.get(peer);\n                        if (ws) {\n                            ws.close();\n                            ws.terminate();\n                        }\n                        _this11.sockets.delete(peer);\n                        peer.markDisconnected().then(function () {\n                            _this11.opts.onPeerRemoved(peer);\n                            resolve(peer);\n                        });\n                    });\n                }));\n            };\n\n            if (!this.iri || !this.iri.isHealthy) {\n                return Promise.resolve(doRemove());\n            }\n\n            return this.iri.removeNeighbors(peers).then(doRemove).catch(doRemove);\n        }\n\n        /**\n         * Randomly removes a given amount of peers from current connections.\n         * Low-quality peers are favored to be removed.\n         * @param {number} amount\n         * @param {boolean} incomingOnly - only drop incoming connections\n         * @param {Peer[]} array - array of connected peers to use for dropping\n         * @returns {Promise.<Peer[]>} removed peers\n         * @private\n         */\n\n    }, {\n        key: '_dropRandomNeighbors',\n        value: function _dropRandomNeighbors() {\n            var amount = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;\n\n            var _this12 = this;\n\n            var incomingOnly = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n            var array = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n\n            var peers = array ? array : incomingOnly ? Array.from(this.sockets.keys()).filter(function (p) {\n                return _this12.sockets.get(p).incoming;\n            }) : array ? array : Array.from(this.sockets.keys());\n            var selectRandomPeer = function selectRandomPeer() {\n                var weights = peers.map(function (p) {\n                    return Math.max(p.getPeerQuality(), 0.0001);\n                });\n                return weighted(peers, weights);\n            };\n            var toRemove = [];\n\n            if (!peers.length) {\n                return Promise.resolve([]);\n            }\n\n            for (var x = 0; x < amount; x++) {\n                var peer = selectRandomPeer();\n                peers.splice(peers.indexOf(peer), 1);\n                toRemove.push(peer);\n            }\n\n            return this._removeNeighbors(toRemove);\n        }\n\n        /**\n         * Connects to a peer, checking if it's online and trying to get its peers.\n         * @param {Peer} peer\n         * @returns {Peer}\n         */\n\n    }, {\n        key: 'connectPeer',\n        value: function connectPeer(peer) {\n            this.log('connecting peer'.yellow, this.formatNode(peer.data.hostname, peer.data.port));\n            var key = peer.data.key || createIdentifier();\n            this.list.update(peer, { dateTried: new Date(), tried: (peer.data.tried || 0) + 1, key: key });\n            this._bindWebSocket(new WebSocket(peer.getNelsonWebsocketURI(), {\n                headers: this._getHeaders(key),\n                handshakeTimeout: 5000\n            }), peer);\n            return peer;\n        }\n\n        /**\n         * Connects the node to a new set of random addresses that comply with the out/in rules.\n         * Up to a soft maximum.\n         * @returns {Peer[]} List of new connected peers\n         */\n\n    }, {\n        key: 'reconnectPeers',\n        value: function reconnectPeers() {\n            var _this13 = this;\n\n            // TODO: remove old peers by inverse weight, maybe? Not urgent. Can be added at a later point.\n            // this.log('reconnectPeers');\n            // If max was reached, do nothing:\n            var toTry = this.opts.outgoingMax - this._getOutgoingSlotsCount();\n\n            if (!this.iri || !this.iri.isHealthy || toTry < 1 || this.isMaster || this._getOutgoingSlotsCount() >= this.opts.outgoingMax) {\n                return [];\n            }\n\n            // Get connectable peers:\n            var list = this.list.all().filter(function (p) {\n                return !p.data.dateTried || getSecondsPassed(p.data.dateTried) > _this13.opts.beatInterval * Math.max(2, 2 * p.data.tried || 0);\n            }).filter(function (p) {\n                return !_this13.iri.isStaticNeighbor(p);\n            });\n\n            // Get allowed peers:\n            return this.list.getWeighted(192, list).filter(function (p) {\n                return !_this13.sockets.get(p[0]);\n            }).slice(0, toTry).map(function (p) {\n                return _this13.connectPeer(p[0]);\n            });\n        }\n\n        /**\n         * Returns a set of peers ready to be shared with their respective weight ratios.\n         * @returns {Array[]}\n         */\n\n    }, {\n        key: 'getPeers',\n        value: function getPeers() {\n            // The node tries to recommend best of the best, even better nodes than it tries to connect, usually.\n            // One tries to be helpful to the others, remember? Only suggesting top-notch peers.\n            return this.list.getWeighted(this.opts.maxShareableNodes, null, 2);\n        }\n\n        /**\n         * Each epoch, disconnect all peers and reconnect new ones.\n         * @private\n         */\n\n    }, {\n        key: '_onEpoch',\n        value: function _onEpoch() {\n            var _this14 = this;\n\n            this.log('new epoch and new id:', this.heart.personality.id);\n            if (!this.isSaturationReached()) {\n                return Promise.resolve(false);\n            }\n            // Master node should recycle all its connections\n            if (this.opts.isMaster) {\n                return this._removeNeighbors(Array.from(this.sockets.keys())).then(function () {\n                    _this14.reconnectPeers();\n                    return false;\n                });\n            }\n            return this._dropRandomNeighbors(getRandomInt(0, this._getOutgoingSlotsCount())).then(function () {\n                _this14.reconnectPeers();\n                return false;\n            });\n        }\n\n        /**\n         * Checks whether expired peers are still connectable.\n         * If not, disconnect/remove them.\n         * @private\n         */\n\n    }, {\n        key: '_onCycle',\n        value: function _onCycle() {\n            var _this15 = this;\n\n            this.log('new cycle');\n            var promises = [];\n            // Remove closed or dead sockets. Otherwise set as not alive and ping:\n            this.sockets.forEach(function (ws, peer) {\n                if (ws.readyState > 1 || !ws.isAlive) {\n                    promises.push(_this15._removeNeighbor(peer));\n                } else if (peer.isLazy()) {\n                    _this15.log(('Peer ' + peer.data.hostname + ' (' + peer.data.name + ') is lazy for more than ' + _this15.opts.lazyLimit + ' seconds. Removing...!').yellow);\n                    promises.push(_this15._removeNeighbor(peer));\n                } else if (ws.readyState === 1) {\n                    ws.isAlive = false;\n                    ws.ping('', false);\n                }\n            });\n            return Promise.all(promises).then(function () {\n                return false;\n            });\n        }\n\n        /**\n         * Try connecting to more peers.\n         * @returns {Promise}\n         * @private\n         */\n\n    }, {\n        key: '_onTick',\n        value: function _onTick() {\n            var _this16 = this;\n\n            terminal.nodes({\n                nodes: this.list.all(),\n                connected: Array.from(this.sockets.keys()).filter(function (p) {\n                    return _this16.sockets.get(p).readyState === 1;\n                }).map(function (p) {\n                    return p.data;\n                })\n            });\n\n            // Try connecting more peers. Master nodes do not actively connect (no outgoing connections).\n            if (!this.opts.isMaster && this._getOutgoingSlotsCount() < this.opts.outgoingMax) {\n                return new Promise(function (resolve) {\n                    _this16.reconnectPeers();\n                    resolve(false);\n                });\n            }\n\n            // If for some reason the maximal nodes were overstepped, drop one.\n            else if (this._getIncomingSlotsCount() > this.opts.incomingMax) {\n                    return this._dropRandomNeighbors(this._getIncomingSlotsCount() - this.opts.incomingMax, true).then(function () {\n                        return false;\n                    });\n                } else {\n                    return Promise.resolve(false);\n                }\n        }\n\n        /**\n         * Callback for IRI to check for health and neighbors.\n         * If unhealthy, disconnect all. Otherwise, disconnect peers that are not in IRI list any more for any reason.\n         * @param {boolean} healthy\n         * @param {object[]} data\n         * @private\n         */\n\n    }, {\n        key: '_onIRIHealth',\n        value: function _onIRIHealth(healthy, data) {\n            var _this17 = this;\n\n            if (!healthy) {\n                // Do not drop connections, yet. IRI might just be unavailable for a moment.\n                // If it still has \"old\" neighbors, they will leak, causing more nodes to be added than permitted.\n                this.log('IRI gone...'.red);\n                return;\n                // return this._removeNeighbors(Array.from(this.sockets.keys()));\n            }\n            Promise.all(data.map(function (n) {\n                return n.address;\n            }).map(getIP)).then(function (neighbors) {\n                var toRemove = [];\n                Array.from(_this17.sockets.keys())\n                // It might be that the neighbour was just added and not yet included in IRI...\n                .filter(function (p) {\n                    return getSecondsPassed(p.data.dateLastConnected) > 5;\n                }).forEach(function (peer) {\n                    if (!neighbors.includes(peer.data.hostname) && (!peer.data.ip || peer.data.ip && !neighbors.includes(peer.data.ip))) {\n                        toRemove.push(peer);\n                    } else {\n                        var index = Math.max(neighbors.indexOf(peer.data.hostname), neighbors.indexOf(peer.data.ip));\n                        index >= 0 && peer.updateConnection(data[index]);\n                    }\n                });\n                if (toRemove.length) {\n                    _this17.log('Disconnecting Nelson nodes that are missing in IRI:'.red, toRemove.map(function (p) {\n                        return p.data.hostname;\n                    }));\n                    return _this17._removeNeighbors(toRemove);\n                }\n            }).then(function () {\n                return _this17.iri.cleanupNeighbors(Array.from(_this17.sockets.keys()));\n            });\n        }\n\n        /**\n         * Returns whether the provided address/port/id matches this node\n         * @param {string} address\n         * @param {number|string} port\n         * @param {string|null} nelsonID\n         * @returns {boolean}\n         */\n\n    }, {\n        key: 'isMyself',\n        value: function isMyself(address, port) {\n            var nelsonID = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;\n\n            var isPrivate = ip.isPrivate(address) || ['127.0.0.1', 'localhost'].includes(address);\n            var sameAddress = isPrivate || address === this.ipv4;\n            var samePort = parseInt(port) === this.opts.port;\n            var sameID = this.heart && this.heart.personality && nelsonID === this.heart.personality.publicId;\n            return sameID || sameAddress && (!this.opts.localNodes || samePort);\n        }\n\n        /**\n         * Returns whether certain address can contact this instance.\n         * @param {string} remoteKey\n         * @param {string} address\n         * @param {number} port\n         * @param {boolean} checkTrust - whether to check for trusted peer\n         * @param {number} easiness - how \"easy\" it is to get in\n         * @returns {Promise<boolean>}\n         */\n\n    }, {\n        key: 'isAllowed',\n        value: function isAllowed(remoteKey, address, port) {\n            var _this18 = this;\n\n            var checkTrust = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;\n            var easiness = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 24;\n\n            var allowed = function allowed() {\n                return getPeerIdentifier(_this18.heart.personality.id + ':' + (_this18.opts.localNodes ? port : address)).slice(0, _this18._getMinEasiness(easiness)).indexOf(_this18.heart.personality.feature) >= 0;\n            };\n\n            return checkTrust ? this.list.findByRemoteKeyOrAddress(remoteKey, address, port).then(function (ps) {\n                return ps.filter(function (p) {\n                    return p.isTrusted();\n                }).length || allowed();\n            }) : Promise.resolve(allowed());\n        }\n\n        /**\n         * Returns whether the amount of connected nodes has reached a certain threshold.\n         * @returns {boolean}\n         */\n\n    }, {\n        key: 'isSaturationReached',\n        value: function isSaturationReached() {\n            var ratioConnected = (this._getOutgoingSlotsCount() + this._getIncomingSlotsCount()) / (this.opts.outgoingMax + this.opts.incomingMax);\n            return ratioConnected >= 0.75;\n        }\n\n        /**\n         * For new nodes, make it easy to find nodes and contact them\n         * @param {number} easiness - how easy it is to get in/out\n         * @returns {number} updated easiness value\n         * @private\n         */\n\n    }, {\n        key: '_getMinEasiness',\n        value: function _getMinEasiness(easiness) {\n            // New nodes are trusting less the incoming connections.\n            // As the node matures in the community, it becomes more welcoming for inbound requests.\n            var l = this.list.all().filter(function (p) {\n                return p.data.connected;\n            }).length;\n            return Math.min(easiness, Math.max(5, parseInt(l / 2)));\n        }\n    }]);\n\n    return Node;\n}(Base);\n\nmodule.exports = {\n    DEFAULT_OPTIONS: DEFAULT_OPTIONS,\n    Node: Node\n};"
  },
  {
    "path": "dist/node/peer-list.js",
    "content": "'use strict';\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\nvar path = require('path');\nvar ip = require('ip');\nvar dns = require('dns');\nvar tmp = require('tmp');\n\nvar _require = require('url'),\n    URL = _require.URL;\n\nvar weighted = require('weighted');\nvar Datastore = require('nedb');\n\nvar _require2 = require('./base'),\n    Base = _require2.Base;\n\nvar _require3 = require('./peer'),\n    Peer = _require3.Peer;\n\nvar _require4 = require('./iri'),\n    DEFAULT_IRI_OPTIONS = _require4.DEFAULT_OPTIONS;\n\nvar _require5 = require('./tools/utils'),\n    getSecondsPassed = _require5.getSecondsPassed,\n    createIdentifier = _require5.createIdentifier;\n\nvar DEFAULT_OPTIONS = {\n    dataPath: path.join(process.cwd(), 'data/neighbors.db'),\n    isMaster: false,\n    multiPort: false,\n    temporary: false,\n    logIdent: 'LIST',\n    ageNormalizer: 3600,\n    lazyLimit: 300, // Time, after which a peer is considered lazy, if no new TXs received\n    lazyTimesLimit: 3 // starts to penalize peer's quality if connected so many times without new TXs\n};\n\n/**\n * A class that manages a list of peers and its persistence in the database\n * @class PeerList\n */\n\nvar PeerList = function (_Base) {\n    _inherits(PeerList, _Base);\n\n    function PeerList(options) {\n        _classCallCheck(this, PeerList);\n\n        var _this = _possibleConstructorReturn(this, (PeerList.__proto__ || Object.getPrototypeOf(PeerList)).call(this, _extends({}, DEFAULT_OPTIONS, options)));\n\n        _this.onPeerUpdate = _this.onPeerUpdate.bind(_this);\n        _this.loaded = false;\n        _this.peers = [];\n\n        _this.db = new Datastore({\n            filename: _this.opts.temporary ? tmp.tmpNameSync() : _this.opts.dataPath,\n            autoload: true\n        });\n        _this.db.persistence.setAutocompactionInterval(30000);\n        return _this;\n    }\n\n    /**\n     * Loads the peer database, preloading defaults, if any.\n     * @param {string[]} defaultPeerURLs\n     * @returns {Promise<Peer>}\n     */\n\n\n    _createClass(PeerList, [{\n        key: 'load',\n        value: function load(defaultPeerURLs) {\n            var _this2 = this;\n\n            return new Promise(function (resolve) {\n                _this2.db.find({}, function (err, docs) {\n                    _this2.peers = docs.map(function (data) {\n                        return new Peer(data, _this2._getPeerOptions());\n                    });\n                    _this2.loadDefaults(defaultPeerURLs).then(function () {\n                        _this2.log('DB and default peers loaded');\n                        _this2.loaded = true;\n                        resolve(_this2.peers);\n                    });\n                });\n            });\n        }\n\n        /**\n         * Adds default peers to the database/list.\n         * @param {string[]} defaultPeerURLs\n         * @returns {Promise<Peer>}\n         */\n\n    }, {\n        key: 'loadDefaults',\n        value: function loadDefaults() {\n            var _this3 = this;\n\n            var defaultPeerURLs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];\n\n            return Promise.all(defaultPeerURLs.map(function (uri) {\n                var tokens = uri.split('/');\n                return _this3.add({\n                    hostname: tokens[0],\n                    port: tokens[1],\n                    TCPPort: tokens[2],\n                    UDPPort: tokens[3],\n                    weight: tokens[4] || 1.0,\n                    IRIProtocol: tokens[5] || 'udp',\n                    isTrusted: true\n                });\n            }));\n        }\n\n        /**\n         * Update callback when the peer's data has been changed from within the peer.\n         * @param peer\n         * @returns {Promise.<Peer>}\n         */\n\n    }, {\n        key: 'onPeerUpdate',\n        value: function onPeerUpdate(peer) {\n            var data = _extends({}, peer.data);\n            delete data._id;\n            return this.update(peer, data, false);\n        }\n\n        /**\n         * Partially updates a peer with the provided data. Saves into database.\n         * @param {Peer} peer\n         * @param {Object} data\n         * @param {boolean} refreshPeer - whether to update the peers data.\n         * @returns {Promise<Peer>}\n         */\n\n    }, {\n        key: 'update',\n        value: function update(peer, data) {\n            var _this4 = this;\n\n            var refreshPeer = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;\n\n            var newData = _extends({}, peer.data, data);\n            return new Promise(function (resolve) {\n                _this4.db.update({ _id: peer.data._id }, newData, { returnUpdatedDocs: true }, function () {\n                    // this.log(`updated peer ${peer.data.hostname}:${peer.data.port}`, JSON.stringify(data));\n                    refreshPeer ? peer.update(newData, false).then(function () {\n                        return resolve(peer);\n                    }) : resolve(peer);\n                });\n            });\n        }\n\n        /**\n         * Returns currently loaded peers.\n         * @returns {Peer[]}\n         */\n\n    }, {\n        key: 'all',\n        value: function all() {\n            return this.peers;\n        }\n\n        /**\n         * Removes all peers.\n         */\n\n    }, {\n        key: 'clear',\n        value: function clear() {\n            var _this5 = this;\n\n            this.log('Clearing all known peers');\n            this.peers = [];\n            return new Promise(function (resolve) {\n                return _this5.db.remove({}, { multi: true }, resolve);\n            });\n        }\n\n        /**\n         * Gets the average age of all known peers\n         * @returns {number}\n         */\n\n    }, {\n        key: 'getAverageAge',\n        value: function getAverageAge() {\n            return this.peers.map(function (p) {\n                return getSecondsPassed(p.data.dateCreated);\n            }).reduce(function (s, x) {\n                return s + x;\n            }, 0) / this.peers.length;\n        }\n\n        /**\n         * Returns peers, whose remoteKey, hostname or IP equals the address.\n         * Port is only considered if multiPort option is true.\n         * If the address/port matches, the remoteKey is not considered.\n         * @param {string} remoteKey\n         * @param {string} address\n         * @param {number} port\n         * @returns {Promise<Peer[]|null>}\n         */\n\n    }, {\n        key: 'findByRemoteKeyOrAddress',\n        value: function findByRemoteKeyOrAddress(remoteKey, address, port) {\n            var _this6 = this;\n\n            return new Promise(function (resolve) {\n                _this6.findByAddress(address, port).then(function (peers) {\n                    if (peers.length) {\n                        return resolve(peers);\n                    }\n                    resolve(_this6.peers.filter(function (p) {\n                        return p.data.remoteKey && p.data.remoteKey === remoteKey;\n                    }));\n                });\n            });\n        }\n\n        /**\n         * Returns peers, whose hostname or IP equals the address.\n         * Port is only considered if mutiPort option is true.\n         * @param {string} address\n         * @param {number} port\n         * @returns {Promise<Peer[]|null>}\n         */\n\n    }, {\n        key: 'findByAddress',\n        value: function findByAddress(address, port) {\n            var _this7 = this;\n\n            var addr = PeerList.cleanAddress(address);\n            return new Promise(function (resolve) {\n                var findWithIP = function findWithIP(ip) {\n                    var peers = _this7.peers.filter(function (p) {\n                        return p.data.hostname === addr || p.data.hostname === address || ip && (p.data.hostname === ip || p.data.ip === ip);\n                    });\n                    resolve(_this7.opts.multiPort ? peers.filter(function (p) {\n                        return p.data.port == port;\n                    }) : peers);\n                };\n\n                if (ip.isV6Format(addr) || ip.isV4Format(addr) || _this7.opts.multiPort) {\n                    findWithIP(addr);\n                } else {\n                    dns.resolve(addr, 'A', function (error, results) {\n                        return findWithIP(error || !results.length ? null : results[0]);\n                    });\n                }\n            });\n        }\n\n        /**\n         * Calculates the trust score of a peer\n         * @param {Peer} peer\n         * @returns {number}\n         */\n\n    }, {\n        key: 'getPeerTrust',\n        value: function getPeerTrust(peer) {\n            var age = parseFloat(getSecondsPassed(peer.data.dateCreated)) / this.opts.ageNormalizer;\n            if (this.opts.isMaster) {\n                var _weightedAge = Math.pow(peer.data.connected || peer.isTrusted() ? age : 0, 2) * Math.pow(peer.getPeerQuality(), 2);\n                return Math.max(_weightedAge, 0.0001);\n            }\n            var weightedAge = Math.pow(age, 2) * Math.pow(peer.getPeerQuality(), 2) * Math.pow(1.0 + peer.data.weight * 10, 2);\n            return Math.max(weightedAge, 0.0001);\n        }\n\n        /**\n         * Get a certain amount of weighted random peers. Return peers with their respective weight ratios\n         * The weight depends on relationship age (connections) and trust (weight).\n         * @param {number} amount\n         * @param {Peer[]} sourcePeers list of peers to use. Optional for filtering purposes.\n         * @param {number} power by which increase the weights\n         * @returns {Array<Peer, number>}\n         */\n\n    }, {\n        key: 'getWeighted',\n        value: function getWeighted() {\n            var amount = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;\n\n            var _this8 = this;\n\n            var sourcePeers = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;\n            var power = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1.0;\n\n            amount = amount || this.peers.length;\n            var peers = sourcePeers || Array.from(this.peers);\n            if (!peers.length) {\n                return [];\n            }\n            var allWeights = peers.map(function (p) {\n                return Math.pow(_this8.getPeerTrust(p), power);\n            });\n            var weightsMax = Math.max.apply(Math, _toConsumableArray(allWeights));\n\n            var choices = [];\n            var getChoice = function getChoice() {\n                var peer = weighted(peers, allWeights);\n                var index = peers.indexOf(peer);\n                var weight = allWeights[index];\n                peers.splice(index, 1);\n                allWeights.splice(index, 1);\n                choices.push([peer, weight / weightsMax]);\n            };\n\n            for (var x = 0; x < amount; x++) {\n                if (peers.length < 1) {\n                    break;\n                }\n                getChoice();\n            }\n            return choices.filter(function (c) {\n                return c && c[0];\n            }).map(function (c) {\n                return [c[0], c[0].isTrusted() ? 1.0 : c[1]];\n            });\n        }\n\n        /**\n         * Adds a new peer to the list using an URI\n         * @param {object} data\n         * @returns {*}\n         */\n\n    }, {\n        key: 'add',\n        value: function add(data) {\n            var _this9 = this;\n\n            var _Object$assign = Object.assign({\n                TCPPort: DEFAULT_IRI_OPTIONS.TCPPort,\n                UDPPort: DEFAULT_IRI_OPTIONS.UDPPort,\n                IRIProtocol: 'udp',\n                isTrusted: false,\n                peerWeight: 0.5,\n                weight: 0,\n                remoteKey: null\n            }, data),\n                hostname = _Object$assign.hostname,\n                rawPort = _Object$assign.port,\n                rawTCPPort = _Object$assign.TCPPort,\n                rawUDPPort = _Object$assign.UDPPort,\n                IRIProtocol = _Object$assign.IRIProtocol,\n                isTrusted = _Object$assign.isTrusted,\n                peerWeight = _Object$assign.peerWeight,\n                weight = _Object$assign.weight,\n                remoteKey = _Object$assign.remoteKey,\n                name = _Object$assign.name;\n\n            var port = parseInt(rawPort);\n            var TCPPort = parseInt(rawTCPPort || DEFAULT_IRI_OPTIONS.TCPPort);\n            var UDPPort = parseInt(rawUDPPort || DEFAULT_IRI_OPTIONS.UDPPort);\n\n            return this.findByRemoteKeyOrAddress(remoteKey, hostname, port).then(function (peers) {\n                var addr = PeerList.cleanAddress(hostname);\n                var existing = peers.length && peers[0];\n\n                if (existing) {\n                    return _this9.update(existing, {\n                        weight: weight ? existing.data.weight ? weight * peerWeight + existing.data.weight * (1.0 - peerWeight) : weight : existing.data.weight,\n                        key: existing.data.key || createIdentifier(),\n                        remoteKey: remoteKey || existing.data.remoteKey,\n                        name: name || existing.data.name,\n                        hostname: addr,\n                        port: port, TCPPort: TCPPort, UDPPort: UDPPort, IRIProtocol: IRIProtocol\n                    });\n                } else {\n                    _this9.log('Adding to the list of known Nelson peers: ' + hostname + ':' + port);\n                    var peerIP = ip.isV4Format(addr) || ip.isV6Format(addr) ? addr : null;\n                    var peer = new Peer({\n                        port: port,\n                        hostname: addr,\n                        ip: peerIP,\n                        TCPPort: TCPPort || DEFAULT_IRI_OPTIONS.TCPPort,\n                        UDPPort: UDPPort || DEFAULT_IRI_OPTIONS.UDPPort,\n                        IRIProtocol: IRIProtocol || 'udp',\n                        isTrusted: isTrusted,\n                        name: name,\n                        weight: weight,\n                        remoteKey: remoteKey,\n                        key: createIdentifier(),\n                        dateCreated: new Date()\n                    }, _this9._getPeerOptions());\n                    _this9.peers.push(peer);\n                    return new Promise(function (resolve, reject) {\n                        _this9.db.insert(peer.data, function (err, doc) {\n                            if (err) {\n                                reject(err);\n                            }\n                            peer.update(doc);\n                            resolve(peer);\n                        });\n                    });\n                }\n            });\n        }\n    }, {\n        key: '_getPeerOptions',\n        value: function _getPeerOptions() {\n            var _opts = this.opts,\n                lazyLimit = _opts.lazyLimit,\n                lazyTimesLimit = _opts.lazyTimesLimit;\n\n            return { lazyLimit: lazyLimit, lazyTimesLimit: lazyTimesLimit, onDataUpdate: this.onPeerUpdate };\n        }\n\n        /**\n         * Converts an address to a cleaner format.\n         * @param {string} address\n         * @returns {string}\n         */\n\n    }], [{\n        key: 'cleanAddress',\n        value: function cleanAddress(address) {\n            if (!ip.isV4Format(address) && !ip.isV6Format(address)) {\n                return address;\n            }\n            return ip.isPrivate(address) ? 'localhost' : address.replace('::ffff:', '');\n        }\n    }]);\n\n    return PeerList;\n}(Base);\n\nmodule.exports = {\n    DEFAULT_OPTIONS: DEFAULT_OPTIONS,\n    PeerList: PeerList\n};"
  },
  {
    "path": "dist/node/peer.js",
    "content": "'use strict';\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return call && (typeof call === \"object\" || typeof call === \"function\") ? call : self; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function, not \" + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }\n\nvar ip = require('ip');\nvar dns = require('dns');\n\nvar _require = require('./base'),\n    Base = _require.Base;\n\nvar _require2 = require('./iri'),\n    DEFAULT_IRI_OPTIONS = _require2.DEFAULT_OPTIONS;\n\nvar _require3 = require('./tools/utils'),\n    getSecondsPassed = _require3.getSecondsPassed,\n    createIdentifier = _require3.createIdentifier;\n\nvar PROTOCOLS = ['tcp', 'udp', 'prefertcp', 'preferudp', 'any'];\nvar DEFAULT_OPTIONS = {\n    onDataUpdate: function onDataUpdate(data) {\n        return Promise.resolve();\n    },\n    ipRefreshTimeout: 1200,\n    silent: true,\n    logIdent: 'PEER',\n    lazyLimit: 300, // Time, after which a peer is considered lazy, if no new TXs received\n    lazyTimesLimit: 3 // starts to penalize peer's quality if connected so many times without new TXs\n};\nvar DEFAULT_PEER_DATA = {\n    name: null,\n    hostname: null,\n    ip: null,\n    port: 31337,\n    TCPPort: DEFAULT_IRI_OPTIONS.TCPPort,\n    UDPPort: DEFAULT_IRI_OPTIONS.UDPPort,\n    IRIProtocol: 'udp', // Assume all old Nelsons to be running udp.\n    seen: 1,\n    connected: 0,\n    tried: 0,\n    weight: 0,\n    dateTried: null,\n    dateLastConnected: null,\n    dateCreated: null,\n    isTrusted: false,\n    key: null,\n    remoteKey: null,\n    lastConnections: []\n};\n\n/**\n * A utility class for a peer that holds peer's data and provides a few util methods.\n *\n * @class Peer\n */\n\nvar Peer = function (_Base) {\n    _inherits(Peer, _Base);\n\n    function Peer() {\n        var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n        var options = arguments[1];\n\n        _classCallCheck(this, Peer);\n\n        var _this = _possibleConstructorReturn(this, (Peer.__proto__ || Object.getPrototypeOf(Peer)).call(this, _extends({}, DEFAULT_OPTIONS, options, { logIdent: data.hostname + ':' + data.port })));\n\n        _this.data = null;\n        _this.lastConnection = null;\n        // Make sure to migrate database if anything else is added to the defaults...\n        _this.update(_extends({}, DEFAULT_PEER_DATA, data));\n        return _this;\n    }\n\n    /**\n     * Partial peer's data update\n     * @param {Object} data\n     * @param {boolean} doCallback - whether to call back on data changes\n     * @returns {Promise<Object>} - updates data\n     */\n\n\n    _createClass(Peer, [{\n        key: 'update',\n        value: function update(data) {\n            var _this2 = this;\n\n            var doCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;\n\n            // Reset last updated date if the hostname has changed\n            var shouldUpdate = false;\n            var hostnameChanged = this.data && this.data.hostname !== (data && data.hostname);\n            this.iplastUpdated = this.data && hostnameChanged ? null : this.iplastUpdated;\n            this.data = _extends({}, this.data, data);\n            if (hostnameChanged && this.data.ip) {\n                this.data.ip = null;\n                shouldUpdate = true;\n            }\n            if (!this.data.ip) {\n                this.data.ip = this._isHostnameIP() ? this.data.hostname : null;\n                shouldUpdate = true;\n            }\n            return shouldUpdate && doCallback ? this.opts.onDataUpdate(this).then(function () {\n                return _this2.data;\n            }) : Promise.resolve(this.data);\n        }\n\n        /**\n         * Gets the IP address of the peer. Independently if peer's address is a hostname or IP.\n         * Update's the peer data to save the last known IP.\n         * @returns {Promise<string>}\n         */\n\n    }, {\n        key: 'getIP',\n        value: function getIP() {\n            var _this3 = this;\n\n            return new Promise(function (resolve) {\n                if (!_this3._hasCorrectIP() || !_this3._isHostnameIP() && _this3._isIPOutdated()) {\n                    dns.resolve(_this3.data.hostname, 'A', function (error, results) {\n                        // if there was an error, we set the hostname as ip, even if it's not the case.\n                        // It will be re-tried next refresh cycle.\n                        var ip = error || !results.length ? null : results[0];\n                        _this3.iplastUpdated = new Date();\n                        if (ip && ip !== _this3.data.ip) {\n                            _this3.data.ip = ip;\n                            _this3.opts.onDataUpdate(_this3).then(function () {\n                                return resolve(ip);\n                            });\n                        } else {\n                            resolve(ip);\n                        }\n                    });\n                } else {\n                    resolve(_this3.data.ip);\n                }\n            });\n        }\n\n        /**\n         * Marks this node as connected.\n         * @returns {Promise.<Peer>}\n         */\n\n    }, {\n        key: 'markConnected',\n        value: function markConnected() {\n            var _this4 = this;\n\n            if (this.lastConnection) {\n                return Promise.resolve(this);\n            }\n            this.lastConnection = {\n                start: new Date(),\n                duration: 0,\n                numberOfAllTransactions: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            };\n\n            return this.update({\n                key: this.data.key || createIdentifier(),\n                tried: 0,\n                connected: this.data.connected + 1,\n                dateLastConnected: new Date()\n            }).then(function () {\n                return _this4;\n            });\n        }\n\n        /**\n         * Marks the node as disconnected. Saves connection stats in DB.\n         * @returns {Promise.<Peer>}\n         */\n\n    }, {\n        key: 'markDisconnected',\n        value: function markDisconnected() {\n            var _this5 = this;\n\n            if (!this.lastConnection) {\n                return Promise.resolve(this);\n            }\n\n            var lastConnections = [].concat(_toConsumableArray(this.data.lastConnections), [_extends({}, this.lastConnection, {\n                end: new Date(),\n                duration: this.getConnectionDuration()\n            })]).slice(-10);\n\n            this.lastConnection = null;\n            return this.update({ lastConnections: lastConnections }).then(function () {\n                return _this5;\n            });\n        }\n\n        /**\n         * Returns time in seconds that passed since the node has been connected\n         * @returns {number}\n         */\n\n    }, {\n        key: 'getConnectionDuration',\n        value: function getConnectionDuration() {\n            if (!this.lastConnection) {\n                return 0;\n            }\n            return getSecondsPassed(this.lastConnection.start);\n        }\n\n        /**\n         * Updates the stats of the currently connected peer\n         * @param data\n         */\n\n    }, {\n        key: 'updateConnection',\n        value: function updateConnection(data) {\n            if (!this.lastConnection) {\n                return;\n            }\n\n            var numberOfAllTransactions = data.numberOfAllTransactions,\n                numberOfRandomTransactionRequests = data.numberOfRandomTransactionRequests,\n                numberOfNewTransactions = data.numberOfNewTransactions,\n                numberOfInvalidTransactions = data.numberOfInvalidTransactions;\n\n            this.lastConnection = _extends({}, this.lastConnection, {\n                numberOfAllTransactions: numberOfAllTransactions,\n                numberOfRandomTransactionRequests: numberOfRandomTransactionRequests,\n                numberOfNewTransactions: numberOfNewTransactions,\n                numberOfInvalidTransactions: numberOfInvalidTransactions\n            });\n        }\n\n        /**\n         * Returns peer's quality based on last connection stats.\n         * @returns {number}\n         */\n\n    }, {\n        key: 'getPeerQuality',\n        value: function getPeerQuality() {\n            var history = [].concat(_toConsumableArray(this.data.lastConnections), [this.lastConnection]).filter(function (h) {\n                return h;\n            });\n            var newTrans = history.reduce(function (s, h) {\n                return s + h.numberOfNewTransactions;\n            }, 0);\n            var badTrans = history.reduce(function (s, h) {\n                return s + h.numberOfInvalidTransactions;\n            }, 0);\n            var rndTrans = history.reduce(function (s, h) {\n                return s + (h.numberOfRandomTransactionRequests || 0);\n            }, 0);\n            var badRatio = parseFloat(badTrans * 5 + rndTrans) / (newTrans || 1);\n            var serialPenalization = !this.isTrusted() && !newTrans && history.length >= this.opts.lazyTimesLimit ? 1.0 / history.length : 1.0;\n            var score = Math.max(0.0, 1.0 / (badRatio || 1)) * serialPenalization;\n            return Math.max(0.01, score);\n        }\n\n        /**\n         * Returns whether a connected peer has not sent any new transactions for a prolonged period of time.\n         * @returns {boolean}\n         */\n\n    }, {\n        key: 'isLazy',\n        value: function isLazy() {\n            return this.lastConnection && getSecondsPassed(this.lastConnection.start) > this.opts.lazyLimit && (this.lastConnection.numberOfNewTransactions === 0 || this.lastConnection.numberOfNewTransactions < this.lastConnection.numberOfRandomTransactionRequests);\n        }\n    }, {\n        key: 'getTCPURI',\n        value: function getTCPURI() {\n            return 'tcp://' + this._getIPString(this.data.hostname) + ':' + this.data.TCPPort;\n        }\n    }, {\n        key: 'getUDPURI',\n        value: function getUDPURI() {\n            return 'udp://' + this._getIPString(this.data.hostname) + ':' + this.data.UDPPort;\n        }\n    }, {\n        key: 'getNelsonURI',\n        value: function getNelsonURI() {\n            return 'http://' + this._getIPString(this.data.hostname) + ':' + this.data.port;\n        }\n    }, {\n        key: 'getNelsonWebsocketURI',\n        value: function getNelsonWebsocketURI() {\n            return 'ws://' + this._getIPString(this.data.hostname) + ':' + this.data.port;\n        }\n    }, {\n        key: 'getHostname',\n        value: function getHostname() {\n            return this.data.hostname + '/' + this.data.port + '/' + this.data.TCPPort + '/' + this.data.UDPPort + '/0/' + this.data.IRIProtocol;\n        }\n    }, {\n        key: 'isTrusted',\n        value: function isTrusted() {\n            return this.data && this.data.isTrusted;\n        }\n    }, {\n        key: 'isSameIP',\n        value: function isSameIP(ip) {\n            return this.getIP().then(function (myIP) {\n                return myIP && myIP === ip;\n            });\n        }\n    }, {\n        key: '_isHostnameIP',\n        value: function _isHostnameIP() {\n            return ip.isV4Format(this.data.hostname) || ip.isV6Format(this.data.hostname);\n        }\n    }, {\n        key: '_hasCorrectIP',\n        value: function _hasCorrectIP() {\n            return this.data.ip && (ip.isV4Format(this.data.ip) || ip.isV6Format(this.data.ip));\n        }\n    }, {\n        key: '_getIPString',\n        value: function _getIPString(ipOrHostname) {\n            return ipOrHostname.includes(':') ? '[' + ipOrHostname + ']' : ipOrHostname;\n        }\n    }, {\n        key: '_isIPOutdated',\n        value: function _isIPOutdated() {\n            return !this.iplastUpdated || getSecondsPassed(this.iplastUpdated) > this.opts.ipRefreshTimeout;\n        }\n    }]);\n\n    return Peer;\n}(Base);\n\nmodule.exports = {\n    DEFAULT_OPTIONS: DEFAULT_OPTIONS,\n    DEFAULT_PEER_DATA: DEFAULT_PEER_DATA,\n    PROTOCOLS: PROTOCOLS,\n    Peer: Peer\n};"
  },
  {
    "path": "dist/node/tools/terminal.js",
    "content": "'use strict';\n\nvar blessed = require('blessed');\nvar contrib = require('blessed-contrib');\nrequire('colors');\nvar moment = require('moment');\nvar momentDurationFormatSetup = require(\"moment-duration-format\");\n\nmomentDurationFormatSetup(moment);\n\nvar screen = null;\nvar mainBox = null;\nvar statusBox = null;\nvar peersBox = null;\nvar progress = null;\n\nmodule.exports = {\n    init: init,\n    exit: ensureScreen(exit),\n    log: log,\n    beat: ensureScreen(beat),\n    settings: ensureScreen(settings),\n    ports: ensureScreen(ports),\n    nodes: ensureScreen(nodes)\n};\n\nfunction init(name, version, onExit) {\n    screen = blessed.screen({\n        smartCSR: true\n    });\n    screen.key(['escape', 'q', 'C-c'], function () {\n        exit();\n        return onExit();\n    });\n\n    mainBox = blessed.box({\n        top: '51%',\n        left: 'center',\n        width: '100%',\n        height: '49%',\n        content: 'Nelson Console:',\n        scrollable: true,\n        alwaysScroll: true,\n        tags: true,\n        border: {\n            type: 'line'\n        },\n        style: {\n            fg: 'white',\n            border: {\n                fg: '#f0f0f0'\n            }\n        }\n    });\n\n    statusBox = blessed.box({\n        top: '0%',\n        left: '0%',\n        width: '30%',\n        height: '51%',\n        content: (name + ' v.' + version + ' - Status').green.bold,\n        tags: true,\n        border: {\n            type: 'line'\n        },\n        style: {\n            fg: 'white',\n            border: {\n                fg: '#f0f0f0'\n            }\n        }\n    });\n\n    peersBox = blessed.box({\n        top: '0%',\n        left: '50%',\n        width: '50%',\n        height: '51%',\n        content: 'Peers'.green.bold,\n        tags: true,\n        border: {\n            type: 'line'\n        },\n        style: {\n            fg: 'white',\n            border: {\n                fg: '#f0f0f0'\n            }\n        }\n    });\n\n    progress = contrib.donut({\n        top: '0%',\n        left: '30%',\n        width: '20%',\n        height: '51%',\n        radius: 8,\n        arcWidth: 3,\n        remainColor: 'black',\n        yPadding: 2,\n        border: {\n            type: 'line'\n        },\n        style: {\n            fg: 'white',\n            border: {\n                fg: '#f0f0f0'\n            }\n        }\n    });\n\n    screen.append(mainBox);\n    screen.append(statusBox);\n    screen.append(peersBox);\n    screen.append(progress);\n    mainBox.focus();\n    screen.render();\n}\n\nfunction log() {\n    var msg = Array.from(arguments).join(' ');\n    if (!screen) {\n        console.log(msg);\n        return;\n    }\n    mainBox.pushLine(msg);\n    mainBox.setScrollPerc(100);\n    screen.render();\n}\n\nfunction beat(_ref) {\n    var epoch = _ref.epoch,\n        cycle = _ref.cycle,\n        startDate = _ref.startDate,\n        pctEpoch = _ref.pctEpoch,\n        pctCycle = _ref.pctCycle;\n\n    var duration = moment.duration(moment().diff(startDate)).format('d [days] h [hours] m [minutes]');\n    statusBox.setLine(3, ('Online: ' + duration).bold.yellow);\n    statusBox.setLine(4, ('Epoch: ' + epoch).bold);\n    statusBox.setLine(5, ('Cycle: ' + cycle).bold);\n    progress.setData([{ percent: pctEpoch, label: 'epoch', color: 'green' }, { percent: pctCycle, label: 'cycle', color: 'green' }]);\n    screen.render();\n}\n\nfunction settings(_ref2) {\n    var epochInterval = _ref2.epochInterval,\n        cycleInterval = _ref2.cycleInterval,\n        startDate = _ref2.startDate;\n\n    var startDateString = moment(startDate).format('dddd, MMMM Do YYYY, HH:mm:ss.SSS');\n    statusBox.setLine(2, ('Started on: ' + startDateString).yellow);\n    statusBox.setLine(6, 'Epoch Interval: ' + epochInterval + 's');\n    statusBox.setLine(7, 'Cycle Interval: ' + cycleInterval + 's');\n    screen.render();\n}\n\nfunction ports(_ref3) {\n    var port = _ref3.port,\n        apiPort = _ref3.apiPort,\n        IRIPort = _ref3.IRIPort,\n        TCPPort = _ref3.TCPPort,\n        UDPPort = _ref3.UDPPort;\n\n    statusBox.setLine(8, ('Port: ' + port).dim.cyan);\n    statusBox.setLine(9, ('API Port: ' + apiPort).dim.cyan);\n    statusBox.setLine(10, ('IRI Port: ' + IRIPort).dim.cyan);\n    statusBox.setLine(11, ('TCP Port: ' + TCPPort).dim.cyan);\n    statusBox.setLine(12, ('UDP Port: ' + UDPPort).dim.cyan);\n    screen.render();\n}\n\nfunction nodes(_ref4) {\n    var nodes = _ref4.nodes,\n        connected = _ref4.connected;\n\n    peersBox.setLine(2, ('Count: ' + nodes.length + ' (Connected: ' + (connected.length || 0) + ')').bold);\n    peersBox.setLine(4, 'Connections:'.bold);\n    var lines = peersBox.getLines().length;\n    for (var i = lines - 1; i >= 5; i--) {\n        peersBox.clearLine(i);\n    }\n    if (!Array.isArray(connected) || connected.length === 0) {\n        peersBox.setLine(5, 'do not worry, this may take a while...'.dim);\n    } else {\n        connected.forEach(function (connection, i) {\n            var id = ((connection.hostname || connection.ip) + ':' + connection.port).bold.cyan;\n            id = connection.name ? (id + ' (' + connection.name + ')').bold.cyan : id;\n            // const weight = `[trust: ${(connection.trust * 100).toFixed(6)}]`.green;\n            peersBox.setLine(5 + i, id + ' -> ' + (connection.IRIProtocol || 'udp'));\n        });\n    }\n    screen.render();\n}\n\nfunction ensureScreen(f) {\n    return function () {\n        if (!screen) {\n            return;\n        }\n        return f.apply(undefined, arguments);\n    };\n}\n\nfunction exit() {\n    screen.destroy();\n    screen = null;\n}"
  },
  {
    "path": "dist/node/tools/utils.js",
    "content": "'use strict';\n\nvar ip = require('ip');\nvar dns = require('dns');\nvar version = require('../../../package.json').version;\nvar crypto = require('crypto');\nvar md5 = require('md5');\n\n/**\n * Resolves IP or hostname to IP. If failed, returns the input.\n * @param {string} ipOrHostName\n * @returns {Promise<string>}\n */\nfunction getIP(ipOrHostName) {\n    return new Promise(function (resolve) {\n        if (ip.isV4Format(ipOrHostName) || ip.isV6Format(ipOrHostName)) {\n            return resolve(ipOrHostName);\n        }\n        dns.resolve(ipOrHostName, 'A', function (error, results) {\n            resolve(error ? ipOrHostName : results[0]);\n        });\n    });\n}\n\n/**\n * Returns number of seconds that passed starting from a given time.\n * @param time\n * @returns {number}\n */\nfunction getSecondsPassed(time) {\n    if (!time) {\n        return 0;\n    }\n    return (new Date().getTime() - time.getTime()) / 1000;\n}\n\n/**\n * Creates a random 96-char-long hexadecimal identifier.\n * @returns {string}\n */\nfunction createIdentifier() {\n    return crypto.randomBytes(48).toString('hex');\n}\n\n/**\n * Creates an MD5 hash from the given address\n * @param {string} address\n * @returns {string}\n */\nfunction getPeerIdentifier(address) {\n    return md5(address);\n}\n\n/**\n * Returns a random number\n * @param {number} min\n * @param {number} max\n * @returns {number}\n */\nfunction getRandomInt(min, max) {\n    min = Math.ceil(min);\n    max = Math.floor(max);\n    return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive\n}\n\n/**\n * Shuffles the array\n * @param {Array} array\n * @returns {Array}\n */\nfunction shuffleArray(array) {\n    return array.sort(function () {\n        return Math.random() - 0.5;\n    });\n}\n\n/**\n * Returns Nelson version number\n */\nfunction getVersion() {\n    return version;\n}\n\n/**\n * Returns whether the provided version number is the same major version as the current Nelson.\n * @param {string} otherVersion\n */\nfunction isSameMajorVersion(otherVersion) {\n    return version.split('.')[0] === otherVersion.split('.')[0];\n}\n\n/**\n * Returns whether the provided string is a valid Nelson neighbor representation\n * @param str\n * @returns {boolean}\n */\nfunction validNeighbor(str) {\n    var tokens = str.split('/');\n    return tokens.length >= 2 && tokens.length <= 5 && Number.isInteger(parseInt(tokens[1])) && (!tokens[2] || Number.isInteger(parseInt(tokens[2]))) && (!tokens[3] || Number.isInteger(parseInt(tokens[3]))) && (!tokens[4] || !!parseFloat(tokens[4]));\n}\n\nmodule.exports = {\n    getIP: getIP,\n    createIdentifier: createIdentifier,\n    getPeerIdentifier: getPeerIdentifier,\n    getRandomInt: getRandomInt,\n    getSecondsPassed: getSecondsPassed,\n    getVersion: getVersion,\n    isSameMajorVersion: isSameMajorVersion,\n    shuffleArray: shuffleArray,\n    validNeighbor: validNeighbor\n};"
  },
  {
    "path": "dist/simulation/bin/nelson.js",
    "content": "#!/usr/bin/env node\n'use strict';\n\nvar program = require('commander');\n\nvar _require = require('../index'),\n    initMockedNode = _require.initMockedNode;\n\nvar version = require('../../../package.json').version;\n\nvar parseNeighbors = function parseNeighbors(val) {\n    return val.split(' ');\n};\nvar parseProtocol = function parseProtocol(val) {\n    return val.toLowerCase();\n};\nvar parseNumber = function parseNumber(v) {\n    return parseInt(v);\n};\n\nprocess.on('unhandledRejection', function (reason, p) {\n    console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);\n    // application specific logging, throwing an error, or other logic here\n});\n\nprogram.version(version).option('-n, --neighbors [value]', 'Trusted neighbors', parseNeighbors, []).option('-c, --cycle [value]', 'Cycle interval in seconds', parseNumber, 10).option('-e, --epoch [value]', 'Epoch interval in seconds', parseNumber, 60).option('-p, --port [value]', 'Nelson port', parseNumber, 14265).option('--IRIProtocol [value]', 'IRI protocol to use: udp or tcp, prefertcp, preferudp or any', parseProtocol, 'any').option('--master [value]', 'Is master node', false).option('-s, --silent [value]', 'Silent', false).parse(process.argv);\n\ninitMockedNode({\n    port: program.port,\n    dataPort: program.dataPort,\n    silent: program.silent,\n    cycleInterval: program.cycle,\n    epochInterval: program.epoch,\n    neighbors: program.neighbors,\n    isMaster: program.master,\n    IRIProtocol: program.IRIProtocol\n});"
  },
  {
    "path": "dist/simulation/bin/network.js",
    "content": "#!/usr/bin/env node\n'use strict';\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nvar program = require('commander');\n\nvar _require = require('../network'),\n    spawnMockedNetwork = _require.spawnMockedNetwork,\n    DEFAULT_OPTS = _require.DEFAULT_OPTS;\n\nvar version = require('../../../package.json').version;\n\nvar parseNumber = function parseNumber(v) {\n    return parseInt(v);\n};\n\nprocess.on('unhandledRejection', function (reason, p) {\n    console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);\n    // application specific logging, throwing an error, or other logic here\n});\n\nprogram.version(version).option('-c, --cycleInterval [value]', 'Cycle interval in seconds', parseNumber, DEFAULT_OPTS.cycleInterval).option('-e, --epochInterval [value]', 'Epoch interval in seconds', parseNumber, DEFAULT_OPTS.epochInterval).option('-p, --startingPort [value]', 'Starting port', parseNumber, DEFAULT_OPTS.startingPort).option('-n, --nodesCount [value]', 'Normal nodes amount', parseNumber, DEFAULT_OPTS.nodesCount).option('-m, --masterNodesCount [value]', 'Master nodes amount', parseNumber, DEFAULT_OPTS.masterNodesCount).option('-s, --silent', 'Silent', DEFAULT_OPTS.silent).parse(process.argv);\n\nvar proc = spawnMockedNetwork(_extends({}, program, {\n    callback: stats.onCallback\n}));\n\nprocess.on('SIGINT', proc.end);\nprocess.on('SIGTERM', proc.end);"
  },
  {
    "path": "dist/simulation/index.js",
    "content": "'use strict';\n\nvar _require = require('./node'),\n    initMockedNode = _require.initMockedNode;\n\nvar _require2 = require('./network'),\n    spawnMockedNetwork = _require2.spawnMockedNetwork;\n\nmodule.exports = {\n    initMockedNode: initMockedNode,\n    spawnMockedNetwork: spawnMockedNetwork\n};"
  },
  {
    "path": "dist/simulation/network.js",
    "content": "'use strict';\n\nvar _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };\n\nfunction _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }\n\nvar _require = require('../node'),\n    utils = _require.utils,\n    peer = _require.peer;\n\nvar _require2 = require('./node'),\n    spawnNode = _require2.spawnNode;\n\nvar DEFAULT_OPTS = {\n    silent: false,\n    cycleInterval: 12,\n    epochInterval: 36,\n    nodesCount: 47,\n    masterNodesCount: 3,\n    startingPort: 14265,\n    nodeStartDelayRange: [0, 6000],\n    callbackInterval: 5000,\n    onStats: function onStats(nodeStats) {},\n    onError: function onError(nodeStats) {}\n};\n\n// TODO: update jsdoc\n/**\n * Initializes and starts a simulation with a set of mocked nodes.\n * First, the master nodes are started all at once. Then the normal nodes are added sequentially\n * in a random interval (using options.nodeStartDelayRange).\n *\n * @param {object} options\n * @param {number} options.nodesCount - the amount of \"normal\" nodes to start\n * @param {number} options.masterNodesCount - the amount of \"master\" nodes to start\n * @param {number} options.startingPort - Port number to start the nodes from incrementally\n * @param {function} options.onStats - periodic callback with connection summary\n * @param {function} options.onError - on child process exit or error\n * @param {boolean} options.callbackInterval - in seconds\n * @param {number[]} options.nodeStartDelayRange - how many ms to wait between normal nodes starting\n * @returns {{stop: (function()), onPeersAdded: (function())}}\n */\nfunction spawnMockedNetwork(options) {\n    var _DEFAULT_OPTS$options = _extends({}, DEFAULT_OPTS, options),\n        nodesCount = _DEFAULT_OPTS$options.nodesCount,\n        masterNodesCount = _DEFAULT_OPTS$options.masterNodesCount,\n        startingPort = _DEFAULT_OPTS$options.startingPort,\n        silent = _DEFAULT_OPTS$options.silent,\n        cycleInterval = _DEFAULT_OPTS$options.cycleInterval,\n        epochInterval = _DEFAULT_OPTS$options.epochInterval,\n        onStats = _DEFAULT_OPTS$options.onStats,\n        onError = _DEFAULT_OPTS$options.onError,\n        callbackInterval = _DEFAULT_OPTS$options.callbackInterval,\n        nodeStartDelayRange = _DEFAULT_OPTS$options.nodeStartDelayRange;\n\n    var baseNodeOptions = { silent: silent, cycleInterval: cycleInterval, epochInterval: epochInterval };\n    var allNodes = [];\n    var masterNodeURIs = [];\n    var stats = {};\n\n    var ended = false;\n    var cbInterval = null;\n\n    var hasEnded = function hasEnded() {\n        return ended;\n    };\n    var prc = function prc(p, port) {\n        p.on('message', function (s) {\n            return stats[port] = s;\n        });\n        p.on('error', onError);\n        p.on('exit', function () {\n            return !hasEnded() && onError();\n        });\n    };\n\n    // Start the master nodes\n    for (var x = 0; x < masterNodesCount; x++) {\n        var port = startingPort + x;\n        var TCPPort = port + 10000;\n        var UDPPort = port + 20000;\n        if (ended) {\n            break;\n        }\n        var node = spawnNode(_extends({}, baseNodeOptions, { port: port, isMaster: true, neighbors: masterNodeURIs }));\n        prc(node, port);\n        allNodes.push(node);\n        masterNodeURIs.push('localhost/' + port + '/' + TCPPort + '/' + UDPPort);\n    }\n\n    // Sequentially start the normal nodes\n    var promise = \".\".repeat(nodesCount).split('').reduce(function (promise, value, y) {\n        return hasEnded() ? promise : promise.then(function (nodes) {\n            return new Promise(function (resolve) {\n                if (hasEnded()) {\n                    return resolve(nodes);\n                }\n                setTimeout(function () {\n                    if (hasEnded()) {\n                        return resolve(nodes);\n                    }\n                    var port = startingPort + masterNodesCount + y;\n                    var TCPPort = port + 10000;\n                    var UDPPort = port + 20000;\n                    var node = spawnNode(_extends({}, baseNodeOptions, {\n                        port: port, TCPPort: TCPPort, UDPPort: UDPPort, neighbors: masterNodeURIs,\n                        IRIProtocol: peer.PROTOCOLS[utils.getRandomInt(0, peer.PROTOCOLS.length)]\n                    }));\n                    prc(node, port);\n                    resolve([].concat(_toConsumableArray(nodes), [node]));\n                }, utils.getRandomInt(nodeStartDelayRange[0], nodeStartDelayRange[1]));\n            });\n        });\n    }, Promise.resolve(allNodes));\n\n    var end = function end() {\n        ended = true;\n        cbInterval && clearInterval(cbInterval);\n        return promise.then(function (nodes) {\n            !silent && console.log('STOPPING NETWORK');\n            nodes.forEach(function (n) {\n                return n.kill();\n            });\n        });\n    };\n\n    if (callbackInterval) {\n        cbInterval = setInterval(function () {\n            return onStats(stats);\n        }, callbackInterval);\n    }\n\n    return {\n        end: end,\n        onPeersAdded: function onPeersAdded() {\n            return promise;\n        },\n        getStats: function getStats() {\n            return stats;\n        },\n        getNodeProcesses: function getNodeProcesses() {\n            return allNodes;\n        }\n    };\n}\n\nmodule.exports = {\n    DEFAULT_OPTS: DEFAULT_OPTS,\n    spawnMockedNetwork: spawnMockedNetwork\n};"
  },
  {
    "path": "dist/simulation/node.js",
    "content": "'use strict';\n\nvar cp = require('child_process');\n\nvar _require = require('../node/__mocks__/node'),\n    Node = _require.Node;\n\n/**\n * Initializes a mocked node with given options\n * @param {object} options - see Node options for details.\n * @returns {Promise<Node>}\n */\n\n\nfunction initMockedNode(options) {\n    var node = new Node(options);\n    return node.start().then(function (n) {\n        n.log('initialized!');\n        return node;\n    });\n}\n\n/**\n * Spawns a node process\n * @param {object} options\n */\nfunction spawnNode() {\n    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n    var silent = arguments[1];\n\n    var opts = [];\n    options.port && opts.push('-p') && opts.push('' + options.port);\n    options.isMaster && opts.push('--master');\n    options.IRIProtocol && opts.push('--IRIProtocol') && opts.push(options.IRIProtocol);\n    options.neighbors && options.neighbors.length && opts.push('-n') && opts.push('' + options.neighbors.join(' '));\n    options.silent && opts.push('-s');\n    options.cycleInterval && opts.push('-c') && opts.push('' + options.cycleInterval);\n    options.epochInterval && opts.push('-e') && opts.push('' + options.epochInterval);\n    return cp.fork(__dirname + '/bin/nelson.js', opts, { silent: silent });\n}\n\nmodule.exports = {\n    spawnNode: spawnNode,\n    initMockedNode: initMockedNode\n};"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"@semkodev/nelson.cli\",\n    \"version\": \"0.4.1\",\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/SemkoDev/nelson.cli.git\"\n    },\n    \"description\": \"P2P manager for IOTA's IRI node\",\n    \"main\": \"dist/index.js\",\n    \"bin\": {\n        \"nelson\": \"./dist/nelson.js\"\n    },\n    \"files\": [\n        \"src\",\n        \"dist\",\n        \"README.md\"\n    ],\n    \"scripts\": {\n        \"test\": \"jest -b\",\n        \"build\": \"rimraf dist/ && babel ./src --out-dir dist/ --ignore ./node_modules --ignore __tests__ --copy-files\",\n        \"make:binaries\": \"rimraf builds/ && pkg -t node6-linux,node6-win,node6-macos -o builds/nelson-`cat package.json | jq -r '.version'` dist/nelson.js\",\n        \"make\": \"npm run test && npm run build && npm run make:binaries\",\n        \"postversion\": \"git push --follow-tags\"\n    },\n    \"keywords\": [\n        \"blockchain\",\n        \"IOTA\",\n        \"tangle\",\n        \"p2p\"\n    ],\n    \"homepage\": \"https://semkodev.com\",\n    \"author\": \"Roman Semko <roman@deviota.com> (http://twitter.com/RomanSemko)\",\n    \"license\": \"ISC\",\n    \"jest\": {\n        \"testMatch\": [\n            \"**/__tests__/**/*-test.js?(x)\"\n        ],\n        \"roots\": [\n            \"src\"\n        ]\n    },\n    \"dependencies\": {\n        \"blessed\": \"^0.1.81\",\n        \"blessed-contrib\": \"^4.8.5\",\n        \"body-parser\": \"^1.18.2\",\n        \"colors\": \"^1.1.2\",\n        \"commander\": \"^2.11.0\",\n        \"express\": \"^4.16.2\",\n        \"express-basic-auth\": \"^1.1.3\",\n        \"external-ip\": \"^1.3.1\",\n        \"helmet\": \"^3.10.0\",\n        \"httpdispatcher\": \"^2.1.1\",\n        \"ini\": \"^1.3.5\",\n        \"iota.lib.js\": \"0.4.7\",\n        \"ip\": \"^1.1.5\",\n        \"json2csv\": \"^3.11.5\",\n        \"md5\": \"^2.2.1\",\n        \"moment\": \"^2.19.4\",\n        \"moment-duration-format\": \"^2.0.1\",\n        \"nedb\": \"^1.8.0\",\n        \"request\": \"^2.83.0\",\n        \"tmp\": \"^0.0.33\",\n        \"weighted\": \"^0.3.0\",\n        \"ws\": \"4.0.0\"\n    },\n    \"devDependencies\": {\n        \"babel-cli\": \"^6.26.0\",\n        \"babel-preset-es2015\": \"^6.24.1\",\n        \"babel-preset-stage-2\": \"^6.24.1\",\n        \"jest\": \"^21.2.1\",\n        \"pkg\": \"^4.3.0-beta.1\",\n        \"rimraf\": \"^2.6.2\"\n    }\n}\n"
  },
  {
    "path": "src/api/__tests__/api-test.js",
    "content": "const request = require('request');\nconst { Node } = require('../../node/node');\nconst { createAPI } = require('../index');\n\njest.mock('../../node/iri');\n\nconst API_DATA = [\n    'config', 'connectedPeers', 'heart', 'iriStats', 'isIRIHealthy', 'name', 'peerStats', 'ready',\n    'totalPeers','version'\n];\n\ndescribe('API', () => {\n    it('should get node info correctly', (done) => {\n        const node = new Node({ silent: true, temporary: true, port: 16601 });\n        const server = createAPI({\n            node,\n            apiPort: 12345,\n            apiHostname: 'localhost'\n        });\n        node.start().then(() => {\n            request.get('http://localhost:12345/', (err, resp, body) => {\n                const answer = JSON.parse(body);\n                const keys = Object.keys(answer);\n                keys.sort();\n                expect(keys).toEqual(API_DATA);\n                server.close();\n                node.end().then(done);\n            });\n        })\n    });\n\n    it('should deny public access to protected when no password set', (done) => {\n        const node = new Node({ silent: true, temporary: true, port: 16602 });\n        const server = createAPI({\n            node,\n            apiPort: 12345,\n            apiHostname: 'localhost',\n            username: 'pass',\n            password: 'pass'\n        });\n        node.start().then(() => {\n            request.get('http://localhost:12345/', (err, resp, body) => {\n                expect(resp.statusCode).toEqual(401);\n                expect(body).toBeFalsy;\n                server.close();\n                node.end().then(done);\n            });\n        })\n    });\n\n    it('should deny public access to protected when wrong pass', (done) => {\n        const node = new Node({ silent: true, temporary: true, port: 16603 });\n        const server = createAPI({\n            node,\n            apiPort: 12345,\n            apiHostname: 'localhost',\n            username: 'pass',\n            password: 'pass'\n        });\n        node.start().then(() => {\n            request.get({\n                url: 'http://localhost:12345/',\n                auth: {\n                    user: 'pass',\n                    pass: 'nopass'\n                }\n            }, (err, resp, body) => {\n                expect(resp.statusCode).toEqual(401);\n                expect(body).toBeFalsy;\n                server.close();\n                node.end().then(done);\n            });\n        })\n    });\n\n    it('should allow access to protected when auth ok', (done) => {\n        const node = new Node({ silent: true, temporary: true, port: 16604 });\n        const server = createAPI({\n            node,\n            apiPort: 12345,\n            apiHostname: 'localhost',\n            username: 'pass',\n            password: 'pass'\n        });\n        node.start().then(() => {\n            request.get({\n                url: 'http://localhost:12345/',\n                auth: {\n                    user: 'pass',\n                    pass: 'pass'\n                }\n            }, (err, resp, body) => {\n                const answer = JSON.parse(body);\n                const keys = Object.keys(answer);\n                keys.sort();\n                expect(keys).toEqual(API_DATA);\n                server.close();\n                node.end().then(done);\n            });\n        })\n    });\n\n    it('should get peer stats info correctly', (done) => {\n        const node = new Node({ silent: true, temporary: true, port: 16605 });\n        const server = createAPI({\n            node,\n            apiPort: 12346,\n            apiHostname: 'localhost'\n        });\n        node.start().then(() => {\n            request.get('http://localhost:12346/peer-stats', (err, resp, body) => {\n                const summary = JSON.parse(body);\n                expect(summary.newNodes).toBeTruthy;\n                expect(summary.activeNodes).toBeTruthy;\n                server.close();\n                node.end().then(done);\n            });\n        })\n    });\n\n    it('should get peers info correctly', (done) => {\n        const node = new Node({ silent: true, temporary: true, port: 16606 });\n        const server = createAPI({\n            node,\n            apiPort: 12347,\n            apiHostname: 'localhost'\n        });\n        node.start().then(() => {\n            request.get('http://localhost:12347/peers', (err, resp, body) => {\n                const peers = JSON.parse(body);\n                expect(Array.isArray(peers)).toBeTruthy;\n                server.close();\n                node.end().then(done);\n            });\n        })\n    });\n});\n"
  },
  {
    "path": "src/api/__tests__/node-test.js",
    "content": "const { Node } = require('../../node/node');\nconst { getSummary, getNodeStats } = require('../node');\n\njest.mock('../../node/iri');\n\nconst ALLOWED_DATA = [\n    'config', 'connectedPeers', 'heart', 'iriStats', 'isIRIHealthy',\n    'name', 'peerStats', 'ready', 'totalPeers', 'version'\n];\n\ndescribe('API Node utils', () => {\n   it('should correctly return summary', (done) => {\n       const node = new Node({ silent: true, temporary: true, port: 16607 });\n       node.start().then(() => {\n           const summary = getSummary(node);\n           expect(summary.newNodes).toBeTruthy;\n           expect(summary.activeNodes).toBeTruthy;\n           node.end().then(done)\n       })\n   });\n\n   it('should correctly return node stats', (done) => {\n       const node = new Node({ silent: true, temporary: true, port: 16608 });\n       node.start().then(() => {\n           const stats = getNodeStats(node);\n           const keys =  Object.keys(stats);\n           keys.sort();\n           expect(keys).toEqual(ALLOWED_DATA);\n           node.end().then(done)\n       })\n   })\n});\n"
  },
  {
    "path": "src/api/__tests__/peer-test.js",
    "content": "const {Peer} = require('../../node/peer');\nconst {getPeerStats} = require('../peer');\n\nconst ALLOWED_DATA = [\n    'IRIProtocol', 'TCPPort', 'UDPPort', 'connected', 'dateCreated', 'dateLastConnected', 'dateTried',\n    'hostname', 'ip', 'isTrusted', 'lastConnections', 'name', 'port', 'protocol', 'seen', 'tried', 'weight'\n];\n\ndescribe('API Peer utils', () => {\n    it('should display only public data', () => {\n        const stats = getPeerStats(new Peer());\n        const keys = Object.keys(stats);\n        keys.sort();\n        expect(keys).toEqual(ALLOWED_DATA);\n    })\n});\n"
  },
  {
    "path": "src/api/__tests__/webhooks-test.js",
    "content": "const express = require('express');\nconst bodyParser = require('body-parser');\nconst { Node } = require('../../node/node');\nconst { startWebhooks } = require('../webhooks');\n\njest.mock('../../node/iri');\n\nconst API_DATA = [\n    'config', 'connectedPeers', 'heart', 'iriStats', 'isIRIHealthy', 'name', 'peerStats', 'ready',\n    'totalPeers','version'\n];\n\ndescribe('API Webhooks', () => {\n    it('should start webhooks correctly', (done) => {\n        const node = new Node({ silent: true, temporary: true, port: 16609 });\n        node.start().then(() => {\n            const startDate = new Date();\n            const hook = startWebhooks(node, [ 'http://localhost:12348' ], 2);\n            const app = express();\n            app.use(bodyParser.urlencoded({ extended: false }));\n            app.use(bodyParser.json());\n\n            const server = app.listen(12348);\n            app.post('/', (req) => {\n                const timePassed = (new Date()) - startDate;\n                const keys = Object.keys(req.body);\n                keys.sort();\n                expect(keys).toEqual(API_DATA);\n                expect(timePassed).toBeGreaterThanOrEqual(2000);\n                expect(timePassed).toBeLessThanOrEqual(2100);\n                server.close();\n                hook.stop();\n                node.end().then(done);\n            });\n        })\n    });\n});\n"
  },
  {
    "path": "src/api/index.js",
    "content": "const express = require('express');\nconst helmet = require('helmet');\nconst bodyParser = require('body-parser');\nconst basicAuth = require('express-basic-auth');\n\nconst { getNodeStats, getSummary } = require('./node');\nconst { getPeerStats } = require('./peer');\nconst { startWebhooks } = require('./webhooks');\n\nconst DEFAULT_OPTIONS = {\n    node: null,\n    webhooks: [],\n    webhookInterval: 30,\n    username: null,\n    password: null,\n    apiPort: 18600,\n    apiHostname: '127.0.0.1'\n};\n\n/**\n * Creates an Express APP instance, also starts regular webhooks callbacks.\n * @param options\n * @returns {*|Function}\n */\nfunction createAPI (options) {\n    const opts = { ...DEFAULT_OPTIONS, ...options };\n\n    // Start webhook callbacks\n    if (opts.webhooks && opts.webhooks.length) {\n        startWebhooks(opts.node, opts.webhooks, opts.webhookInterval)\n    }\n\n    // Start API server\n    const app = express();\n    app.set('node', opts.node);\n\n    // Basic app protection\n    app.use(helmet());\n\n    // Enable basic HTTP Auth\n    if (opts.username && opts.password) {\n        app.use(basicAuth({\n            users: { [opts.username]: opts.password }\n        }))\n    }\n\n    // parse application/x-www-form-urlencoded\n    app.use(bodyParser.urlencoded({ extended: false }));\n\n    // parse application/json\n    app.use(bodyParser.json());\n\n    //////////////////////// ENDPOINTS ////////////////////////\n\n    app.get('/', (req, res) => {\n        res.json(getNodeStats(opts.node))\n    });\n\n    app.get('/peer-stats', (req, res) => {\n        res.json(getSummary(opts.node))\n    });\n\n    app.get('/peers', (req, res) => {\n        res.json(opts.node.list.all().map(getPeerStats))\n    });\n\n    return app.listen(opts.apiPort, opts.apiHostname);\n}\n\nmodule.exports = {\n    createAPI,\n    DEFAULT_OPTIONS\n};\n"
  },
  {
    "path": "src/api/node.js",
    "content": "const { getPeerStats } = require('./peer');\nconst version = require('../../package.json').version;\n\n/**\n * Returns summary of the node stats\n * @param {Node} node\n * @returns {{newNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}, activeNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}}}\n */\nfunction getSummary (node) {\n    const now = new Date();\n    const hour = 3600000;\n    const hourAgo = new Date(now - hour);\n    const fourAgo = new Date(now - (hour * 4));\n    const twelveAgo = new Date(now - (hour * 12));\n    const dayAgo = new Date(now - (hour * 24));\n    const weekAgo = new Date(now - (hour * 24 * 7));\n    return {\n        newNodes: {\n            hourAgo: node.list.all().filter(p => p.data.dateCreated >= hourAgo).length,\n            fourAgo: node.list.all().filter(p => p.data.dateCreated >= fourAgo).length,\n            twelveAgo: node.list.all().filter(p => p.data.dateCreated >= twelveAgo).length,\n            dayAgo: node.list.all().filter(p => p.data.dateCreated >= dayAgo).length,\n            weekAgo: node.list.all().filter(p => p.data.dateCreated >= weekAgo).length,\n        },\n        activeNodes: {\n            hourAgo: node.list.all().filter(p => p.data.dateLastConnected >= hourAgo).length,\n            fourAgo: node.list.all().filter(p => p.data.dateLastConnected >= fourAgo).length,\n            twelveAgo: node.list.all().filter(p => p.data.dateLastConnected >= twelveAgo).length,\n            dayAgo: node.list.all().filter(p => p.data.dateLastConnected >= dayAgo).length,\n            weekAgo: node.list.all().filter(p => p.data.dateLastConnected >= weekAgo).length,\n        }\n    }\n}\n\n/**\n * Returns clean node stats to be used in the API\n * @param {Node} node\n * @returns {{name, version, ready: (boolean|*|null), isIRIHealthy: (*|boolean), iriStats: *, peerStats: {newNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}, activeNodes: {hourAgo, fourAgo, twelveAgo, dayAgo, weekAgo}}, totalPeers, connectedPeers: Array, config: {cycleInterval: (Command.opts.cycleInterval|*), epochInterval: (Command.opts.epochInterval|*), beatInterval: (Command.opts.beatInterval|*), dataPath: (Command.opts.dataPath|*), port: (Command.opts.port|*), apiPort: (Command.opts.apiPort|*), IRIPort: (Command.opts.IRIPort|*), TCPPort: (Command.opts.TCPPort|*), UDPPort: (Command.opts.UDPPort|*), IRIProtocol: (Command.opts.IRIProtocol|*), isMaster: (Command.opts.isMaster|*), temporary: (Command.opts.temporary|*)}, heart: {lastCycle: (heart.lastCycle|Heart.lastCycle|_require2.Heart.lastCycle), lastEpoch: (heart.lastEpoch|Heart.lastEpoch|_require2.Heart.lastEpoch), personality: (heart.personality|Heart.personality|_require2.Heart.personality), currentCycle: (heart.currentCycle|Heart.currentCycle|_require2.Heart.currentCycle), currentEpoch: (heart.currentEpoch|Heart.currentEpoch|_require2.Heart.currentEpoch), startDate: (heart.startDate|Heart.startDate|_require2.Heart.startDate)}}}\n */\nfunction getNodeStats (node) {\n    const {\n        cycleInterval,\n        epochInterval,\n        beatInterval,\n        dataPath,\n        port,\n        apiPort,\n        IRIPort,\n        TCPPort,\n        UDPPort,\n        isMaster,\n        IRIProtocol,\n        temporary\n    } = node.opts;\n    const {\n        lastCycle,\n        lastEpoch,\n        personality,\n        currentCycle,\n        currentEpoch,\n        startDate\n    } = node.heart;\n    const totalPeers = node.list.all().length;\n    const isIRIHealthy = node.iri && node.iri.isHealthy;\n    const iriStats = node.iri && node.iri.iriStats;\n    const connectedPeers = Array.from(node.sockets.keys())\n        .filter((p) => node.sockets.get(p).readyState === 1)\n        .map(getPeerStats);\n\n    return {\n        name: node.opts.name,\n        version,\n        ready: node._ready,\n        isIRIHealthy,\n        iriStats,\n        peerStats: getSummary(node),\n        totalPeers,\n        connectedPeers,\n        config: {\n            cycleInterval,\n            epochInterval,\n            beatInterval,\n            dataPath,\n            port,\n            apiPort,\n            IRIPort,\n            TCPPort,\n            UDPPort,\n            IRIProtocol,\n            isMaster,\n            temporary\n        },\n        heart: {\n            lastCycle,\n            lastEpoch,\n            personality,\n            currentCycle,\n            currentEpoch,\n            startDate\n        }\n    }\n}\n\nmodule.exports = {\n    getSummary,\n    getNodeStats\n};\n"
  },
  {
    "path": "src/api/peer.js",
    "content": "/**\n * Returns a clean Peer object that can be used in the API\n * @param {Peer} peer\n * @returns {{name, hostname, ip, port, TCPPort, UDPPort, protocol, IRIProtocol, seen, connected, tried, weight, dateTried, dateLastConnected, dateCreated, isTrusted, lastConnections}}\n */\nfunction getPeerStats (peer) {\n    const {\n        name,\n        hostname,\n        ip,\n        port,\n        TCPPort,\n        UDPPort,\n        protocol,\n        seen,\n        connected,\n        tried,\n        weight,\n        dateTried,\n        dateLastConnected,\n        dateCreated,\n        IRIProtocol,\n        isTrusted,\n        lastConnections\n    } = peer.data;\n    return {\n        name,\n        hostname,\n        ip,\n        port,\n        TCPPort,\n        UDPPort,\n        protocol,\n        IRIProtocol,\n        seen,\n        connected,\n        tried,\n        weight,\n        dateTried,\n        dateLastConnected,\n        dateCreated,\n        isTrusted,\n        lastConnections\n    }\n}\n\nmodule.exports = {\n    getPeerStats\n};\n"
  },
  {
    "path": "src/api/utils.js",
    "content": ""
  },
  {
    "path": "src/api/webhooks.js",
    "content": "const request = require('request');\nconst { getNodeStats } = require('./node');\n\nfunction startWebhooks (node, webhooks, webhookInterval) {\n    const interval = setInterval(() => {\n        webhooks.forEach((uri) => request({\n            uri,\n            method: 'POST',\n            json: getNodeStats(node)\n        }, (err) => {\n            if (err) {\n                node.log(`Webhook returned error: ${uri}`.yellow);\n            }\n        }));\n    }, webhookInterval * 1000);\n    return {\n        stop: () => { clearInterval(interval) }\n    }\n}\n\nmodule.exports = {\n    startWebhooks\n};\n"
  },
  {
    "path": "src/index.js",
    "content": "require('colors');\nconst request = require('request');\nconst terminal = require('./node/tools/terminal');\nconst node = require('./node').node;\nconst api = require('./api/index');\nconst utils = require('./node').utils;\n\n// Some general TODOs:\n// TODO: add linting\n// TODO: add editor config\n\nmodule.exports = {\n    initNode: (opts) => {\n        const init = (options) => {\n            const _node = new node.Node(options);\n            const terminate = () => _node.end().then(\n                () => {\n                    process.exit(0);\n                }\n            );\n\n            process.on('SIGINT', terminate);\n            process.on('SIGTERM', terminate);\n            opts.gui && terminal.init(opts.name, utils.getVersion(), terminate);\n\n            _node.start().then((n) => {\n                api.createAPI({\n                    node: n,\n                    webhooks: opts.webhooks,\n                    webhookInterval: opts.webhookInterval,\n                    apiPort: opts.apiPort,\n                    apiHostname: opts.apiHostname,\n                    username: opts.apiAuth && opts.apiAuth.username,\n                    password: opts.apiAuth && opts.apiAuth.password,\n                });\n                terminal.ports(n.opts);\n                n.log(`Nelson v.${utils.getVersion()} initialized`.green.bold);\n            });\n        };\n\n        if (opts.getNeighbors) {\n            if (typeof opts.getNeighbors === 'boolean') {\n                opts.getNeighbors = 'https://raw.githubusercontent.com/SemkoDev/nelson.cli/master/ENTRYNODES'\n            }\n            let neighbors = [];\n            request(opts.getNeighbors, (err, resp, body) => {\n                if (err) {\n                    throw err\n                }\n                neighbors = body.split('\\n').map((str) => {\n                    if (!str || !str.length) {\n                        return null;\n                    }\n                    if (utils.validNeighbor(str)) {\n                        console.log('Downloaded entry neighbor:', str);\n                        return str;\n                    }\n                    else {\n                        console.log('Wrong entry neighbor format:', str);\n                        return null;\n                    }\n                }).filter(n => n);\n                opts.neighbors = [ ...(opts.neighbors ? opts.neighbors : []), ...neighbors ];\n                init(opts);\n            });\n        }\n        else {\n            init(opts);\n        }\n\n    },\n    ...node\n};\n"
  },
  {
    "path": "src/nelson.js",
    "content": "#!/usr/bin/env node\nrequire('colors');\n\nconst ini = require('ini');\nconst fs = require('fs');\nconst { URL } = require('url');\nconst program = require('commander');\nconst { initNode } = require('./index');\nconst { DEFAULT_OPTIONS } = require('./node/node');\nconst { PROTOCOLS } = require('./node/peer');\nconst { DEFAULT_OPTIONS: DEFAULT_LIST_OPTIONS } = require('./node/peer-list');\nconst { DEFAULT_OPTIONS: DEFAULT_API_OPTIONS } = require('./api/index');\nconst version = require('../package.json').version;\n\nconst parseNeighbors = (val) => val.split(' ');\nconst parseURLs = (val) => val.split(' ').map((v) => new URL(v)).map((u) => u.href);\nconst parseProtocol = (val) => {\n    const lower = val.toLowerCase();\n    return PROTOCOLS.includes(lower) ? lower : DEFAULT_OPTIONS.IRIProtocol\n};\nconst parseNumber = (v) => parseInt(v);\nconst parseAuth = (v) => {\n  const tokens = v.split(':');\n  if(!tokens.length === 2) {\n      throw new Error('Wrong apiAuth format! Use: \"username.password\"');\n  }\n  if (!tokens[0].length) {\n      throw new Error('apiAuth username not provided!');\n  }\n  if(!tokens[1].length) {\n      throw new Error('apiAuth password not provided!');\n  }\n  return { username: tokens[0], password: tokens[1]}\n};\n\nprogram\n    .version(version)\n    .option('--name [value]', 'Name of your node instance', DEFAULT_OPTIONS.name)\n    .option('-n, --neighbors [value]', 'Trusted neighbors', parseNeighbors, [])\n    .option('--getNeighbors [url]', 'Download default set of neighbors', false)\n    .option('-c, --cycleInterval [value]', 'Cycle interval in seconds', parseNumber, DEFAULT_OPTIONS.cycleInterval)\n    .option('-e, --epochInterval [value]', 'Epoch interval in seconds', parseNumber, DEFAULT_OPTIONS.epochInterval)\n    .option('--incomingMax [value]', 'Maximal incoming connection slots', parseNumber, DEFAULT_OPTIONS.incomingMax)\n    .option('--outgoingMax [value]', 'Maximal outgoing connection slots', parseNumber, DEFAULT_OPTIONS.outgoingMax)\n    .option('--lazyLimit [value]', 'Seconds after which neighbor is dropped for not having provided any new TXs', parseNumber, DEFAULT_OPTIONS.lazyLimit)\n    .option('--lazyTimesLimit [value]', 'How many consecutive times a lazy neighbor can connect before getting penalized', parseNumber, DEFAULT_OPTIONS.lazyTimesLimit)\n    .option('--apiAuth [value]', 'Nelson API username:password', parseAuth, null)\n    .option('-a, --apiPort [value]', 'Nelson API port', parseNumber, DEFAULT_API_OPTIONS.apiPort)\n    .option('-o, --apiHostname [value]', 'Nelson API hostname', DEFAULT_API_OPTIONS.apiHostname)\n    .option('-w, --webhooks [value]', 'Nelson API webhook URLs', parseURLs, DEFAULT_API_OPTIONS.webhooks)\n    .option('--webhookInterval [value]', 'Webhooks callback interval in seconds', parseNumber, DEFAULT_API_OPTIONS.webhookInterval)\n    .option('-p, --port [value]', 'Nelson port', parseNumber, DEFAULT_OPTIONS.port)\n    .option('-r, --IRIHostname [value]', 'IRI API hostname', DEFAULT_OPTIONS.IRIHostname)\n    .option('-i, --IRIPort [value]', 'IRI API port', parseNumber, DEFAULT_OPTIONS.IRIPort)\n    .option('-t, --TCPPort [value]', 'IRI TCP port', parseNumber, DEFAULT_OPTIONS.TCPPort)\n    .option('-u, --UDPPort [value]', 'IRI UDP port', parseNumber, DEFAULT_OPTIONS.UDPPort)\n    .option('--IRIProtocol [value]', 'IRI protocol to use: udp, tcp, prefertcp, preferudp or any', parseProtocol, DEFAULT_OPTIONS.IRIProtocol)\n    .option('-d, --dataPath [value]', 'Peer database path', DEFAULT_LIST_OPTIONS.dataPath)\n    .option('-m, --isMaster [value]', 'Is a master node', false)\n    .option('-s, --silent [value]', 'Silent', false)\n    .option('-g, --gui [value]', 'GUI', false)\n    .option('--temporary [value]', 'Create a temporary node', false)\n    .option('--config [value]', 'Config file path', null)\n    .parse(process.argv);\n\nconst configPath = process.env.NELSON_CONFIG || program.config;\n\ninitNode(configPath ? ini.parse(fs.readFileSync(configPath, 'utf-8')).nelson : program);\n"
  },
  {
    "path": "src/node/__mocks__/iri.js",
    "content": "const req = require.requireActual ? require.requireActual : require;\nconst { IRI: BaseIRI, DEFAULT_OPTIONS } = req('../iri');\n\n/**\n * Class responsible to RUN and communicate with local IRI instance\n * @class\n */\nclass IRI extends BaseIRI {\n\n    /**\n     * Starts the IRI process, returning self on success.\n     * @returns {Promise<IRI>}\n     */\n    start () {\n        return new Promise((resolve) => {\n            this._isStarted = true;\n            this.isHealthy = true;\n            this.ticker = setInterval(this._tick, 15000);\n            this.getStats().then(() => resolve(this));\n        })\n    }\n\n    /**\n     * Removes a list of neighbors from IRI, except static neighbors. Returns list of removed peers.\n     * @param {Peer[]} peers\n     * @returns {Promise<Peer[]>}\n     */\n    removeNeighbors (peers) {\n        if (!this.isAvailable()) {\n            return Promise.reject();\n        }\n\n        return new Promise ((resolve) => {\n            resolve(peers)\n        });\n    }\n\n    /**\n     * Adds a list of peers to IRI.\n     * @param {Peer[]} peers\n     * @returns {Promise<Peer[]>}\n     */\n    addNeighbors (peers) {\n        if (!this.isAvailable()) {\n            return Promise.reject();\n        }\n\n        return new Promise((resolve) => {\n            resolve(peers);\n        });\n    }\n\n    /**\n     * Cleans up any orphans from the IRI\n     * @param {Peer[]} peers\n     * @returns {Promise<URL[]>}\n     */\n    cleanupNeighbors (peers) {\n        if (!this.isAvailable()) {\n            return Promise.reject();\n        }\n        return new Promise((resolve) => {\n            resolve([]);\n        });\n    }\n\n    /**\n     * Updates the list of neighbors at the IRI backend. Removes all neighbors, replacing them with\n     * the newly provided neighbors.\n     * @param {Peer[]} peers\n     * @returns {Promise<Peer[]>}\n     */\n    updateNeighbors (peers) {\n        if (!this.isAvailable()) {\n            return Promise.reject();\n        }\n\n        if (!peers || !peers.length) {\n            return Promise.resolve([]);\n        }\n\n        return new Promise((resolve, reject) => {\n            const addNeighbors = () => {\n                this.addNeighbors(peers).then(resolve).catch(reject);\n            };\n\n            addNeighbors();\n        });\n    }\n\n    /**\n     * Removes all IRI neighbors, except static neighbors.\n     * @returns {Promise}\n     */\n    removeAllNeighbors () {\n        if (!this.isAvailable()) {\n            return Promise.reject();\n        }\n\n        return new Promise((resolve) => {\n            resolve();\n        });\n    }\n\n    /**\n     * Returns IRI node info\n     * @returns {Promise<object>}\n     */\n    getStats () {\n        return new Promise((resolve) => {\n            this.iriStats = { mock: true };\n            resolve(this.iriStats);\n        });\n    }\n\n    _tick () {\n        const { onHealthCheck } = this.opts;\n        this.getStats().then(() => {\n            onHealthCheck(true, []);\n        });\n    }\n\n}\n\nIRI.isMocked = true;\n\nmodule.exports = {\n    IRI,\n    DEFAULT_OPTIONS\n};\n"
  },
  {
    "path": "src/node/__mocks__/node.js",
    "content": "const { Node: BaseNode, DEFAULT_OPTIONS: DEFAULT_NODE_OPTIONS } = require('../node');\nconst { getRandomInt } = require('../tools/utils');\nconst { IRI } = require('./iri');\n\nconst DEFAULT_OPTIONS = {\n    ...DEFAULT_NODE_OPTIONS,\n    localNodes: true,\n    beatInterval: 2,\n    cycleInterval: 3,\n    epochInterval: 30,\n    lazyLimit: 6,\n    testnet: true,\n    temporary: true,\n};\n\n/**\n * This is a mock for the \"real\" node. What it does are several things:\n *\n * 1. Mock away IRI backend so we do not start it. We just want to test the P2P functionality.\n * 2. Create a separate neighbor database for each node.\n * 3. Report stats to the parent process\n *\n * @class Node\n */\nclass Node extends BaseNode {\n    constructor (options) {\n        super({ ...DEFAULT_OPTIONS, ...options });\n        this.sendStats = this.sendStats.bind(this);\n        setInterval(this.sendStats, 1000);\n    }\n\n    _getIRI () {\n        const { APIPort, TCPPort, UDPPort, testnet, silent, temporary } = this.opts;\n\n        return (new IRI({\n            APIPort, TCPPort, UDPPort, testnet, silent, temporary,\n            logIdent: `${this.opts.port}::IRI`\n        })).start().then((iri) => {\n            this.iri = iri;\n            return iri;\n        })\n    }\n\n    _setPublicIP () {\n        this.ipv4 = 'localhost';\n        return Promise.resolve(0);\n    }\n\n    _onIRIHealth () {\n        Array.from(this.sockets.keys()).forEach((peer) => {\n            peer.updateConnection({\n                numberOfAllTransactions: getRandomInt(0, 1000),\n                numberOfNewTransactions: getRandomInt(0, 150),\n                numberOfRandomTransactionRequests: getRandomInt(0, 100),\n                numberOfInvalidTransactions: getRandomInt(0, 10)\n            });\n        })\n    }\n\n    /////////////////////////////////// MOCK SPECIFICS ///////////////////////////////////\n\n    sendStats () {\n        const sockets = Array.from(this.sockets.values());\n\n        process.send({\n            isMaster: this.opts.isMaster,\n            peers: this.list ? this.list.all().map((p) => p.data.port) : [],\n            connections: {\n                list: Array.from(this.sockets.keys()).filter(k => this.sockets.get(k).readyState === 1).map(\n                    (peer) => `${peer.data.port}`\n                ),\n                connecting: sockets.filter(s => s.readyState === 0).length,\n                connected: sockets.filter(s => s.readyState === 1).length,\n                closed: sockets.filter(s => s.readyState > 1).length\n            }\n        });\n    }\n}\n\nmodule.exports = {\n    DEFAULT_OPTIONS,\n    Node\n};\n"
  },
  {
    "path": "src/node/__tests__/guard-test.js",
    "content": "const { Guard } = require('../guard');\n\ndescribe('Guard', () => {\n    it('should guard correctly', (done) => {\n        const guard = new Guard();\n        expect(guard.isAllowed('localhost')).toBeTruthy;\n        expect(guard.isAllowed('localhost')).toBeFalsy;\n        setTimeout(() => {\n            expect(guard.isAllowed('localhost')).toBeTruthy;\n            expect(guard.isAllowed('localhost')).toBeFalsy;\n            setTimeout(() => {\n                expect(guard.isAllowed('localhost')).toBeFalsy;\n                done();\n            }, 1001);\n        }, 2001);\n    });\n});\n"
  },
  {
    "path": "src/node/__tests__/heart-test.js",
    "content": "const { Heart } = require('../heart');\n\njasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;\n\ndescribe('Heart', () => {\n    it('doesnt tick, if not started', (done) => {\n       const heart = new Heart({ cycleInterval: 1, epochInterval: 3, silent: true });\n       setTimeout(() => {\n           expect(heart.personality.id).toBeFalsy;\n           done();\n       }, 4000);\n    }, 5000);\n\n    it('ticks when started', (done) => {\n        const heart = new Heart({ cycleInterval: 1, epochInterval: 3, silent: true, autoStart: true });\n        setTimeout(() => {\n            expect(heart.personality.id).toBeTruthy;\n            done();\n        }, 2000);\n    }, 3000);\n\n    it('ticks when started #2', (done) => {\n        const heart = new Heart({ cycleInterval: 1, epochInterval: 3, silent: true });\n        heart.start();\n        setTimeout(() => {\n            expect(heart.personality.id).toBeTruthy;\n            done();\n        }, 2000);\n    }, 3000);\n\n/*    it('updates personality correctly', (done) => {\n        const heart = new Heart({ cycleInterval: 1, epochInterval: 2, silent: true, autoStart: true });\n        const p1 = heart.personality;\n\n        setTimeout(() => {\n            const p2 = heart.personality;\n            expect(p1).not.toEqual(p2);\n            setTimeout(() => {\n                const p3 = heart.personality;\n                expect(p3).not.toEqual(p2);\n                expect(p3).not.toEqual(p1);\n                done();\n            }, 2100);\n        }, 2100);\n    }, 10000);\n*/\n    it('Does not update personality, if epoch off', (done) => {\n        const heart = new Heart({\n            cycleInterval: 1,\n            epochInterval: 2,\n            silent: true,\n            autoStart: true,\n            onEpoch: () => Promise.resolve(true)\n        });\n        const p1 = heart.personality;\n\n        setTimeout(() => {\n            const p2 = heart.personality;\n            expect(p1).toEqual(p2);\n            setTimeout(() => {\n                const p3 = heart.personality;\n                expect(p3).toEqual(p2);\n                done();\n            }, 2100);\n        }, 2100);\n    }, 7000);\n\n    it('Does not update personality #2, if cycle off', (done) => {\n        const heart = new Heart({\n            cycleInterval: 1,\n            epochInterval: 2,\n            silent: true,\n            autoStart: true,\n            onCycle: () => Promise.resolve(true)\n        });\n        const p1 = heart.personality;\n\n        setTimeout(() => {\n            const p2 = heart.personality;\n            expect(p1).toEqual(p2);\n            setTimeout(() => {\n                const p3 = heart.personality;\n                expect(p3).toEqual(p2);\n                done();\n            }, 2100);\n        }, 2100);\n    }, 7000);\n});\n"
  },
  {
    "path": "src/node/__tests__/node-test.js",
    "content": "const { Node } = require('../node');\nconst { IRI } = require('../iri');\n\njest.mock('../iri');\n\nconst DEFAULT_OPTIONS = {\n    localNodes: true,\n    beatInterval: 2,\n    cycleInterval: 3,\n    epochInterval: 30,\n    lazyLimit: 6,\n    testnet: true,\n    temporary: true,\n};\n\ndescribe('Node', () => {\n    it('should mock IRI correctly', () => {\n        expect(IRI.isMocked).toBeTruthy;\n    });\n\n    it('should initialize Node correctly', (done) => {\n        const node = new Node({ ...DEFAULT_OPTIONS, silent: true, port: 16610 });\n        node.start().then((n) => {\n            expect(n.iri && n.iri.isAvailable()).toBeTruthy;\n            expect(n.heart && n.heart.personality && n.heart.personality.id).toBeTruthy;\n            node.end().then(done);\n        }).catch(done);\n    });\n});\n"
  },
  {
    "path": "src/node/__tests__/peer-list-test.js",
    "content": "const dns = require(\"dns\");\nconst { PeerList } = require(\"../peer-list\");\n\njasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;\n\ndescribe(\"PeerListTest\", () => {\n    it(\"should create a list correctly\", done => {\n        const list = new PeerList({ temporary: true, silent: true });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            expect(list.peers[0].data.key).toBeTruthy;\n            expect(list.peers[1].data.key).toBeTruthy;\n            expect(list.peers).toHaveLength(2);\n            expect(list.peers.map(p => p.data.hostname).sort()).toEqual(\n                [\"somehost.com\", \"122.232.223.0\"].sort()\n            );\n            expect(list.peers.map(p => p.data.port).sort()).toEqual(\n                [1234, 14265].sort()\n            );\n            done();\n        });\n    });\n\n    it(\"should add to a list correctly\", done => {\n        const list = new PeerList({ temporary: true, silent: true });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            expect(list.peers).toHaveLength(2);\n            expect(list.peers.map(p => p.data.hostname).sort()).toEqual(\n                [\"somehost.com\", \"122.232.223.0\"].sort()\n            );\n            list.add({\n                hostname: \"some-other-peer.org\",\n                port: 334,\n                TCPPort: 335,\n                UDPPort: 336\n            }).then(() => {\n                expect(list.peers[0].data.key).toBeTruthy;\n                expect(list.peers[1].data.key).toBeTruthy;\n                expect(list.peers[2].data.key).toBeTruthy;\n                expect(list.peers.map(p => p.data.hostname).sort()).toEqual(\n                    [\n                        \"somehost.com\",\n                        \"122.232.223.0\",\n                        \"some-other-peer.org\"\n                    ].sort()\n                );\n                expect(list.peers.map(p => p.data.port).sort()).toEqual(\n                    [334, 1234, 14265].sort()\n                );\n                done();\n            });\n        });\n    });\n\n    it(\"should return all peers\", done => {\n        const list = new PeerList({ temporary: true, silent: true });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            expect(list.all()).toHaveLength(2);\n            done();\n        });\n    });\n\n    it(\"should return average age correctly\", done => {\n        const list = new PeerList({ temporary: true, silent: true });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            setTimeout(() => {\n                list.add({\n                    hostname: \"somehost2.com\",\n                    port: 1234,\n                    TCPPort: 666,\n                    UDPPort: 777\n                }).then(() => {\n                    expect(list.peers).toHaveLength(3);\n                    expect(list.getAverageAge()).toBeGreaterThan(2.6);\n                    done();\n                });\n            }, 4000);\n        });\n    });\n\n    it(\"should update the list correctly\", done => {\n        const list = new PeerList({ temporary: true, silent: true });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            list.add({\n                hostname: \"somehost.com\",\n                port: 1234,\n                TCPPort: 666,\n                UDPPort: 777\n            }).then(() => {\n                expect(list.peers).toHaveLength(2);\n                expect(list.all().sort()[1].data.TCPPort).toEqual(666);\n                done();\n            });\n        });\n    });\n\n    it(\"should update the list correctly by key\", done => {\n        const list = new PeerList({ temporary: true, silent: true });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            list.add({\n                hostname: \"somehost.com\",\n                port: 1234,\n                TCPPort: 666,\n                UDPPort: 777,\n                remoteKey: \"213213\"\n            }).then(() => {\n                list.add({\n                    hostname: \"someanotherhost.com\",\n                    port: 1234,\n                    TCPPort: 668,\n                    UDPPort: 777,\n                    remoteKey: \"213213\"\n                }).then(() => {\n                    expect(list.peers).toHaveLength(2);\n                    expect(list.all().sort()[1].data.TCPPort).toEqual(668);\n                    done();\n                });\n            });\n        });\n    });\n\n    it(\"should add to the list correctly, same host, when multiPort allowed\", done => {\n        const list = new PeerList({\n            temporary: true,\n            multiPort: true,\n            silent: true\n        });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            list.add({\n                hostname: \"somehost.com\",\n                port: 12345,\n                TCPPort: 333,\n                UDPPort: 444\n            }).then(() => {\n                expect(list.peers).toHaveLength(3);\n                expect(list.all()[0].data.port).toEqual(1234);\n                expect(list.all()[2].data.port).toEqual(12345);\n                done();\n            });\n        });\n    });\n\n    it(\"should update a peer correctly\", done => {\n        const list = new PeerList({ temporary: true, silent: true });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            const peer = list.all()[1];\n            const data = { ...peer.data, port: 6789 };\n            list.update(peer, { port: 6789 }).then(() => {\n                expect(list.all()[1].data).toEqual(data);\n                done();\n            });\n        });\n    });\n\n    it(\"should find in list correctly\", done => {\n        const list = new PeerList({ temporary: true, silent: true });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            list.findByAddress(\"somehost.com\", 2345).then(peers => {\n                expect(peers).toHaveLength(1);\n                done();\n            });\n        });\n    });\n\n    it(\"should find in list with unknown remote key, but existing address\", done => {\n        const list = new PeerList({ temporary: true, silent: true });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            list.findByRemoteKeyOrAddress(\"abcdef\", \"somehost.com\", 2345).then(\n                peers => {\n                    expect(peers).toHaveLength(1);\n                    done();\n                }\n            );\n        });\n    });\n\n    it(\"should find in list with known remote key, but unknown address\", done => {\n        const list = new PeerList({ temporary: true, silent: true });\n        list.add({\n            hostname: \"somehost.com\",\n            port: 12345,\n            TCPPort: 333,\n            UDPPort: 444,\n            remoteKey: \"abcdef\"\n        }).then(() => {\n            list.findByRemoteKeyOrAddress(\n                \"abcdef\",\n                \"unknownhost.com\",\n                2345\n            ).then(peers => {\n                expect(peers).toHaveLength(1);\n                expect(peers[0].data.hostname).toEqual(\n                    list.all()[0].data.hostname\n                );\n                done();\n            });\n        });\n    });\n\n    it(\"should not find in list with unknown remote key and address\", done => {\n        const list = new PeerList({ temporary: true, silent: true });\n        list.add({\n            hostname: \"somehost.com\",\n            port: 12345,\n            TCPPort: 333,\n            UDPPort: 444,\n            remoteKey: \"abcdef\"\n        }).then(() => {\n            list.findByRemoteKeyOrAddress(\"xyz\", \"unknownhost.com\", 2345).then(\n                peers => {\n                    expect(peers).toHaveLength(0);\n                    done();\n                }\n            );\n        });\n    });\n\n    it(\"should not find in list correctly, multiPort + different ports\", done => {\n        const list = new PeerList({\n            temporary: true,\n            multiPort: true,\n            silent: true\n        });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            list.findByAddress(\"somehost.com\", 2345).then(peers => {\n                expect(peers).toHaveLength(0);\n                done();\n            });\n        });\n    });\n\n    it(\"should find in list correctly by using IP\", done => {\n        const list = new PeerList({\n            temporary: true,\n            multiPort: false,\n            silent: true\n        });\n        list.load([\n            \"deviota.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\",\n            \"iota.org/123/345/546\"\n        ]).then(() => {\n            Promise.all(list.peers.map(p => p.getIP())).then(() => {\n                dns.resolve(\"deviota.com\", \"A\", (error, results) => {\n                    const ip = error || !results.length ? null : results[0];\n                    if (ip) {\n                        list.findByAddress(ip, 2345).then(peers => {\n                            expect(peers).toHaveLength(1);\n                            done();\n                        });\n                    } else {\n                        done();\n                    }\n                });\n            });\n        });\n    });\n\n    it(\"should return correct peer trusts\", done => {\n        const list = new PeerList({\n            temporary: true,\n            ageNormalizer: 60,\n            silent: true\n        });\n        list.load([\n            \"somehost.com/1234/345/567\",\n            \"122.232.223.0/14265/11111/22222\"\n        ]).then(() => {\n            setTimeout(() => {\n                list.add({\n                    hostname: \"some-other-peer.org\",\n                    port: 334,\n                    TCPPort: 335,\n                    UDPPort: 336\n                }).then(() => {\n                    setTimeout(() => {\n                        list.add({\n                            hostname: \"some-other-peer2.org\",\n                            port: 334,\n                            TCPPort: 335,\n                            UDPPort: 336\n                        }).then(() => {\n                            expect(\n                                list.getPeerTrust(list.peers[0])\n                            ).toBeGreaterThan(0.5);\n                            expect(\n                                list.getPeerTrust(list.peers[1])\n                            ).toBeGreaterThan(0.5);\n                            expect(\n                                list.getPeerTrust(list.peers[2])\n                            ).toBeGreaterThan(0.0001);\n                            expect(\n                                list.getPeerTrust(list.peers[2])\n                            ).toBeLessThan(0.01);\n                            expect(\n                                list.getPeerTrust(list.peers[3])\n                            ).toBeGreaterThanOrEqual(0.0001);\n                            expect(\n                                list.getPeerTrust(list.peers[3])\n                            ).toBeLessThan(0.001);\n                            done();\n                        });\n                    }, 1000);\n                });\n            }, 3000);\n        });\n    });\n\n    it(\n        \"should return correct peer weights\",\n        done => {\n            const list = new PeerList({\n                temporary: true,\n                ageNormalizer: 60,\n                silent: true\n            });\n            list.load([\n                \"somehost.com/1234/345/567\",\n                \"122.232.223.0/14265/11111/22222\"\n            ]).then(() => {\n                setTimeout(() => {\n                    list.add({\n                        hostname: \"some-other-peer.org\",\n                        port: 334,\n                        TCPPort: 335,\n                        UDPPort: 336\n                    }).then(() => {\n                        setTimeout(() => {\n                            list.add({\n                                hostname: \"some-other-peer2.org\",\n                                port: 334,\n                                TCPPort: 335,\n                                UDPPort: 336\n                            }).then(() => {\n                                const weights = list\n                                    .getWeighted()\n                                    .map(w => w[1])\n                                    .sort();\n                                weights.reverse();\n                                expect(weights[0]).toEqual(1);\n                                expect(weights[1]).toEqual(1);\n                                expect(weights[2]).toBeCloseTo(0.002, 3);\n                                expect(weights[3]).toBeCloseTo(0.000075, 4);\n                                done();\n                            });\n                        }, 3000);\n                    });\n                }, 3000);\n            });\n        },\n        12000\n    );\n});\n"
  },
  {
    "path": "src/node/__tests__/peer-test.js",
    "content": "const dns = require(\"dns\");\nconst { Peer, DEFAULT_PEER_DATA } = require(\"../peer\");\n\njasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;\n\ndescribe(\"Peer\", () => {\n    it(\"should add default peer data\", () => {\n        const peer = new Peer();\n        expect(peer.data).toEqual(DEFAULT_PEER_DATA);\n    });\n\n    it(\"should add non-default data to peer\", () => {\n        const peer = new Peer({ hostname: \"tangle.com\", port: 666 });\n        expect(peer.data.hostname).toEqual(\"tangle.com\");\n        expect(peer.data.port).toEqual(666);\n    });\n\n    it(\"should return correct tcp, http and hostname strings\", () => {\n        const peer = new Peer({\n            hostname: \"tangle.com\",\n            port: 666,\n            TCPPort: 777,\n            UDPPort: 888\n        });\n        expect(peer.getTCPURI()).toEqual(\"tcp://tangle.com:777\");\n        expect(peer.getUDPURI()).toEqual(\"udp://tangle.com:888\");\n        expect(peer.getNelsonURI()).toEqual(\"http://tangle.com:666\");\n        expect(peer.getNelsonWebsocketURI()).toEqual(\"ws://tangle.com:666\");\n        expect(peer.getHostname()).toEqual(\"tangle.com/666/777/888/0/udp\");\n    });\n\n    it(\"should update the peer data\", () => {\n        const peer = new Peer({ hostname: \"tangle.com\", port: 666 });\n        expect(peer.data.hostname).toEqual(\"tangle.com\");\n        peer.update({ hostname: \"iota.org\" });\n        expect(peer.data.hostname).toEqual(\"iota.org\");\n        expect(peer.data.port).toEqual(666);\n    });\n\n    it(\"should reset IP if hostname changed\", () => {\n        const peer = new Peer({\n            hostname: \"tangle.com\",\n            port: 666,\n            ip: \"123.123.123.123\"\n        });\n        expect(peer.data.ip).toEqual(\"123.123.123.123\");\n        peer.update({ hostname: \"iota.org\" });\n        expect(peer.data.ip).toEqual(null);\n    });\n\n    it(\"should return a resolved ip if hostname is an ip\", done => {\n        const peer = new Peer({ hostname: \"192.168.0.1\", port: 666 });\n        peer.getIP().then(ip => {\n            expect(ip).toEqual(\"192.168.0.1\");\n            done();\n        });\n    });\n\n    it(\"should return a resolved ip if hostname is not an ip\", done => {\n        const peer = new Peer({ hostname: \"deviota.com\", port: 666 });\n        dns.resolve(peer.data.hostname, \"A\", (error, results) => {\n            peer.getIP().then(ip => {\n                expect(ip).toEqual(results[0]);\n                done();\n            });\n        });\n    });\n\n    it(\"should compare an ip-hostname correctly\", done => {\n        const peer = new Peer({ hostname: \"192.168.0.1\", port: 666 });\n        peer.isSameIP(\"192.168.0.1\").then(result => {\n            expect(result).toBeTruthy;\n            done();\n        });\n    });\n\n    it(\"should compare an ip-hostname correctly #2\", done => {\n        const peer = new Peer({ hostname: \"192.168.0.1\", port: 666 });\n        peer.isSameIP(\"192.168.0.2\").then(result => {\n            expect(result).toBeFalsy;\n            done();\n        });\n    });\n\n    it(\"should compare a hostname and ip correctly\", done => {\n        const peer = new Peer({ hostname: \"deviota.com\", port: 666 });\n        dns.resolve(peer.data.hostname, \"A\", (error, results) => {\n            peer.isSameIP(results[0]).then(result => {\n                expect(result).toBeTruthy;\n                done();\n            });\n        });\n    });\n\n    it(\n        \"should record connection data\",\n        done => {\n            const peer = new Peer({\n                hostname: \"tangle.com\",\n                port: 666,\n                TCPPort: 777,\n                UDPPort: 888\n            });\n            recordPeerConnection(peer, 3).then(() => {\n                expect(peer.data.lastConnections).toHaveLength(1);\n                done();\n            });\n        },\n        4000\n    );\n\n    it(\n        \"should record multiple connection data\",\n        done => {\n            const peer = new Peer({\n                hostname: \"tangle.com\",\n                port: 666,\n                TCPPort: 777,\n                UDPPort: 888\n            });\n            const durations = [1, 4, 2];\n            const datas = [null, null, null];\n            recordPeerConnections(peer, durations, datas).then(() => {\n                expect(peer.data.lastConnections).toHaveLength(3);\n                done();\n            });\n        },\n        8000\n    );\n\n    it(\"should mark a peer as lazy\", done => {\n        const peer = new Peer(\n            { hostname: \"tangle.com\", port: 666, TCPPort: 777, UDPPort: 888 },\n            {\n                lazyLimit: 3\n            }\n        );\n        peer.markConnected().then(() => {\n            peer.updateConnection({\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            });\n            setTimeout(() => {\n                expect(peer.isLazy()).toBeFalsy;\n                setTimeout(() => {\n                    expect(peer.isLazy()).toBeTruthy;\n                    done();\n                }, 2100);\n            }, 1000);\n        });\n    });\n\n    it(\"should correctly calculate quality #1\", done => {\n        const peer = new Peer({\n            hostname: \"tangle.com\",\n            port: 666,\n            TCPPort: 777,\n            UDPPort: 888\n        });\n        const durations = [1, 1, 1];\n        const datas = [\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            },\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            },\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            }\n        ];\n        recordPeerConnections(peer, durations, datas).then(() => {\n            expect(peer.getPeerQuality()).toBeCloseTo(0.3333, 4);\n            done();\n        });\n    });\n\n    it(\"should correctly calculate quality #2\", done => {\n        const peer = new Peer({\n            hostname: \"tangle.com\",\n            port: 666,\n            TCPPort: 777,\n            UDPPort: 888\n        });\n        const durations = [1, 1];\n        const datas = [\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            },\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            }\n        ];\n        recordPeerConnections(peer, durations, datas).then(() => {\n            expect(peer.getPeerQuality()).toBeCloseTo(1.0, 4);\n            done();\n        });\n    });\n\n    it(\"should correctly calculate quality #3\", done => {\n        const peer = new Peer({\n            hostname: \"tangle.com\",\n            port: 666,\n            TCPPort: 777,\n            UDPPort: 888\n        });\n        const durations = [1, 1, 1, 1, 1];\n        const datas = [\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            },\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            },\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            },\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            },\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            }\n        ];\n        recordPeerConnections(peer, durations, datas).then(() => {\n            expect(peer.getPeerQuality()).toBeCloseTo(0.2, 4);\n            done();\n        });\n    });\n\n    it(\"should correctly calculate quality #4\", done => {\n        const peer = new Peer({\n            hostname: \"tangle.com\",\n            port: 666,\n            TCPPort: 777,\n            UDPPort: 888\n        });\n        const durations = [1, 1, 1, 1, 1];\n        const datas = [\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            },\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            },\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            },\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 0,\n                numberOfInvalidTransactions: 0\n            },\n            {\n                numberOfAllTransactions: 0,\n                numberOfRandomTransactionRequests: 0,\n                numberOfNewTransactions: 10,\n                numberOfInvalidTransactions: 2\n            }\n        ];\n        recordPeerConnections(peer, durations, datas).then(() => {\n            expect(peer.getPeerQuality()).toBeCloseTo(1, 4);\n            done();\n        });\n    });\n});\n\nfunction recordPeerConnections(peer, durations, datas) {\n    expect(durations.length).toEqual(datas.length);\n    return durations.reduce(\n        (promise, duration, index) =>\n            promise.then(() =>\n                recordPeerConnection(peer, duration, datas[index])\n            ),\n        Promise.resolve()\n    );\n}\n\nfunction recordPeerConnection(peer, duration, data) {\n    return new Promise(resolve => {\n        peer.markConnected().then(() => {\n            peer.updateConnection(\n                data || {\n                    numberOfAllTransactions: 0,\n                    numberOfRandomTransactionRequests: 0,\n                    numberOfNewTransactions: 0,\n                    numberOfInvalidTransactions: 0\n                }\n            );\n            setTimeout(() => {\n                expect(peer.getConnectionDuration()).toBeCloseTo(duration, 1);\n                peer.markDisconnected().then(() => {\n                    resolve(peer);\n                });\n            }, duration * 1000);\n        });\n    });\n}\n"
  },
  {
    "path": "src/node/base.js",
    "content": "require('colors');\nconst terminal = require('./tools/terminal');\n\nconst DEFAULT_OPTIONS = {\n    silent: false,\n    logIdent: 'BASE',\n    logIdentWidth: 12,\n};\n\n/**\n * Base class with generic functionality.\n * @class Base\n */\nclass Base {\n    constructor (options) {\n        this.opts = { ...DEFAULT_OPTIONS, ...options };\n    }\n\n    log () {\n        if (!this.opts || !this.opts.silent || arguments[0] === '!!') {\n            const date = new Date();\n            const timeString = `${date.toLocaleTimeString()}.${this.formatMilliseconds(date.getMilliseconds())}`.dim;\n            const space = this.opts.logIdent.length > this.opts.logIdentWidth\n                ? `\\n${' '.repeat(this.opts.logIdentWidth)}`\n                : ' '.repeat(this.opts.logIdentWidth - this.opts.logIdent.length);\n            const logIdent = `${this.opts.logIdent}${space}`.dim.bold;\n\n            terminal.log(`${timeString}\\t${logIdent}`, ...arguments);\n        }\n    }\n\n    formatNode (hostname, port) {\n        return `${hostname}:${port}`.cyan\n    }\n\n    formatMilliseconds(milliseconds){\n      var formatted = milliseconds / 1000;\n      formatted = formatted.toFixed(3);\n      formatted = formatted.toString();\n      return formatted.slice(2);\n    }\n\n    start () {}\n\n    end () {}\n}\n\nmodule.exports = {\n    DEFAULT_OPTIONS,\n    Base\n};\n"
  },
  {
    "path": "src/node/guard.js",
    "content": "const { Base } = require('./base');\nconst { getSecondsPassed } = require('./tools/utils');\n\nconst DEFAULT_OPTIONS = {\n    beatInterval: 1,\n    throttleInterval: 2, // Minimal amount of beats to pass until a remote address is allowed again.\n    localNodes: false,\n    logIdent: 'GUARD',\n};\n\n/**\n * Simple throttling system for incoming connections.\n * @class Heart\n */\nclass Guard extends Base {\n    constructor (options) {\n        super({ ...DEFAULT_OPTIONS, ...options });\n        this.requests = {};\n    }\n\n    isAllowed (address, port) {\n        const target = `${this.opts.localNodes ? port : address}`;\n        if (!this.requests[target]) {\n            this.requests[target] = new Date();\n            return true;\n        }\n        else {\n            const allowed = getSecondsPassed(this.requests[target]) >= this.opts.beatInterval * this.opts.throttleInterval;\n            this.requests[target] = new Date();\n            return allowed;\n        }\n    }\n}\n\nmodule.exports = {\n    DEFAULT_OPTIONS,\n    Guard\n};\n"
  },
  {
    "path": "src/node/heart.js",
    "content": "const { Base } = require('./base');\nconst { getSecondsPassed, getRandomInt, createIdentifier } = require('./tools/utils');\nconst terminal = require('./tools/terminal');\n\nconst DEFAULT_OPTIONS = {\n    cycleInterval: 300,\n    epochInterval: 900,\n    beatInterval: 1,\n    autoStart: false,\n    logIdent: 'HEART',\n    onEpoch: (currentEpoch) => Promise.resolve(false),\n    onCycle: (currentCycle) => Promise.resolve(false),\n    onTick: (currentCycle) => Promise.resolve(0),\n};\n\n/**\n * Manages epoch and cycle updates\n * @class Heart\n */\nclass Heart extends Base {\n    constructor (options) {\n        super({ ...DEFAULT_OPTIONS, ...options });\n        this.id = null;\n        this.ticker = null;\n        this.lastCycle = null;\n        this.lastEpoch = null;\n        this.personality = {};\n        this.currentCycle = 0;\n        this.currentEpoch = 0;\n        this.startDate = null;\n        this._tick = this._tick.bind(this);\n        this.opts.autoStart && this.start()\n    }\n\n    start () {\n        this.startDate = new Date();\n        this.startNewEpoch();\n        this.lastCycle = new Date();\n        this.log('Cycle/epoch intervals:', this.opts.cycleInterval, this.opts.epochInterval);\n        terminal.settings({\n            epochInterval: this.opts.epochInterval,\n            cycleInterval: this.opts.cycleInterval,\n            startDate: this.startDate\n        });\n        this._tick();\n    }\n\n    end () {\n        this.ticker && clearTimeout(this.ticker)\n    }\n\n    /**\n     * Starts new epoch, resetting node identifiers and memorizing last epoch switch datetime.\n     */\n    startNewEpoch () {\n        this.setNewPersonality();\n        this.lastEpoch = new Date();\n        this.currentEpoch += 1;\n    }\n\n    /**\n     * Sets this heart's personality: ID, feature, etc.\n     */\n    setNewPersonality () {\n        const id = createIdentifier();\n        this.personality = {\n            id,\n            publicId: id.slice(0, 8),\n            feature: id[getRandomInt(0, id.length)]\n        };\n        this.log('new personality', this.personality.feature, this.personality.id);\n    }\n\n    /**\n     * Ticker that handles cycle and epoch changes.\n     * @private\n     */\n    _tick () {\n        this.opts.onTick(this.currentCycle).then(() => {\n            const passedSecondsEpoch = getSecondsPassed(this.lastEpoch);\n            const passedSecondsCycle = getSecondsPassed(this.lastCycle);\n            const pctEpoch = passedSecondsEpoch / this.opts.epochInterval;\n            const pctCycle = passedSecondsCycle / this.opts.cycleInterval;\n            terminal.beat({\n                epoch: this.currentEpoch,\n                cycle: this.currentCycle,\n                startDate: this.startDate,\n                pctEpoch,\n                pctCycle\n            });\n\n            if (passedSecondsCycle > this.opts.cycleInterval) {\n                this.opts.onCycle(this.currentCycle).then((skipABeat) => {\n                    if (!skipABeat) {\n                        this.lastCycle = new Date();\n                        this.currentCycle += 1;\n                        if (passedSecondsEpoch > this.opts.epochInterval) {\n                            this.opts.onEpoch(this.currentEpoch).then((skipAge) => {\n                                !skipAge && this.startNewEpoch();\n                                this._setTicker();\n                            });\n                            return;\n                        }\n                    }\n                    this._setTicker();\n                });\n                return;\n            }\n            this._setTicker();\n        });\n    }\n\n    /**\n     * Sets the ticker for the next beat\n     * @private\n     */\n    _setTicker () {\n        this.ticker && clearTimeout(this.ticker);\n        this.ticker = setTimeout(this._tick, this.opts.beatInterval * 1000);\n    }\n}\n\nmodule.exports = {\n    DEFAULT_OPTIONS,\n    Heart\n};\n"
  },
  {
    "path": "src/node/index.js",
    "content": "const base = require('./base');\nconst heart = require('./heart');\nconst iri = require('./iri');\nconst node = require('./node');\nconst peer = require('./peer');\nconst peerList = require('./peer-list');\nconst utils = require('./tools/utils');\n\nmodule.exports = {\n    base,\n    heart,\n    iri,\n    node,\n    peer,\n    peerList,\n    utils\n};\n"
  },
  {
    "path": "src/node/iri.js",
    "content": "const IOTA = require('iota.lib.js');\nconst { URL } = require('url');\nconst tmp = require('tmp');\nconst { Base } = require('./base');\nconst { getIP } = require('./tools/utils');\n\ntmp.setGracefulCleanup();\n\nconst DEFAULT_OPTIONS = {\n    hostname: 'localhost',\n    port: 14265,\n    TCPPort: 15600,\n    UDPPort: 14600,\n    logIdent: 'IRI',\n    onHealthCheck: (isHealthy, neighbors) => {}\n};\n\n/**\n * Class responsible to RUN and communicate with local IRI instance\n * @class\n */\nclass IRI extends Base {\n    constructor (options) {\n        super({ ...DEFAULT_OPTIONS, ...options });\n        this.api = (new IOTA({ host: `http://${this.opts.hostname}`, port: this.opts.port })).api;\n        this.removeNeighbors = this.removeNeighbors.bind(this);\n        this.addNeighbors = this.addNeighbors.bind(this);\n        this.updateNeighbors = this.updateNeighbors.bind(this);\n        this._tick = this._tick.bind(this);\n        this._getIRIPeerURI = this._getIRIPeerURI.bind(this);\n        this.ticker = null;\n        this.isHealthy = false;\n        this.iriStats = {};\n        this.staticNeighbors = [];\n    }\n\n    /**\n     * Starts the IRI process, returning self on success.\n     * @returns {Promise<IRI>}\n     */\n    start () {\n        return new Promise((resolve) => {\n            const getNodeInfo = () => this.api.getNeighbors((error, neighbors) => {\n                if (!error) {\n                    const addresses = neighbors.map((n) => (new URL(`${n.connectionType}://${n.address}`)).hostname);\n                    Promise.all(addresses.map(getIP)).then((ips) => {\n                        this._isStarted = true;\n                        this.isHealthy = true;\n                        this.staticNeighbors = ips.concat(addresses);\n                        this.log(`Static neighbors: ${addresses}`);\n                        // TODO: make ticker wait for result, like in the heart.\n                        this.ticker = setInterval(this._tick, 15000);\n                        this.getStats().then(() => resolve(this));\n                    });\n                } else {\n                    this.log(`IRI not ready on ${this.opts.hostname}:${this.opts.port}, retrying...`.yellow);\n                    setTimeout(getNodeInfo, 5000);\n                }\n            });\n            getNodeInfo();\n        })\n    }\n\n    end () {\n        this.isHealthy = false;\n        this._isStarted = false;\n        this.staticNeighbors = [];\n        this.ticker && clearTimeout(this.ticker);\n        this.ticker = null;\n    }\n\n    /**\n     * Returns whether the process has been started.\n     * @returns {boolean}\n     */\n    isStarted () {\n        return this._isStarted\n    }\n\n    /**\n     * Returns whether the IRI process is running and can be communicated with.\n     * @returns {boolean}\n     */\n    isAvailable () {\n        return this.isStarted() && this.isHealthy\n    }\n\n    /**\n     * Returns whether a peer's IP or hostname is added as static neighbor in IRI.\n     * @param {Peer} peer\n     * @returns {boolean}\n     */\n    isStaticNeighbor (peer) {\n        return !!this.staticNeighbors.filter((n) => n === peer.data.ip || n === peer.data.hostname).length;\n    }\n\n    /**\n     * Removes a list of neighbors from IRI, except static neighbors. Returns list of removed peers.\n     * @param {Peer[]} peers\n     * @returns {Promise<Peer[]>}\n     */\n    removeNeighbors (peers) {\n        if (!this.isAvailable()) {\n            return Promise.reject();\n        }\n\n        const myPeers = peers.filter((peer) => {\n            if (this.isStaticNeighbor(peer)) {\n                this.log(`WARNING: trying to remove a static neighbor. Skipping: ${peer.data.hostname}`.yellow);\n                return false;\n            }\n            return true;\n        });\n\n        if (!peers.length) {\n            return Promise.resolve([]);\n        }\n\n        const uris = myPeers.map(this._getIRIPeerURI);\n        return new Promise ((resolve, reject) => {\n            this.api.removeNeighbors(uris, (err) => {\n                if (err) {\n                    reject(err);\n                    return;\n                }\n                this.log('Neighbors removed (if there were any):'.red, uris.join(', '));\n                resolve(peers)\n            });\n        });\n    }\n\n    /**\n     * Adds a list of peers to IRI.\n     * @param {Peer[]} peers\n     * @returns {Promise<Peer[]>}\n     */\n    addNeighbors (peers) {\n        if (!this.isAvailable()) {\n            return Promise.reject();\n        }\n\n        const uris = peers.map(this._getIRIPeerURI);\n\n        return new Promise((resolve, reject) => {\n            this.api.addNeighbors(uris, (error) => {\n                if(error) {\n                    reject(error);\n                    return;\n                }\n                this.log('Neighbors added:'.green, uris.join(', '));\n                resolve(peers);\n            });\n        });\n    }\n\n    /**\n     * Cleans up any orphans from the IRI\n     * @param {Peer[]} peers\n     * @returns {Promise<URL[]>}\n     */\n    cleanupNeighbors (peers) {\n        if (!this.isAvailable()) {\n            return Promise.reject();\n        }\n        return new Promise((resolve) => {\n            this.api.getNeighbors((error, neighbors) => {\n                if(error) {\n                    return resolve();\n                }\n                Promise.all(neighbors.map((n) => {\n                    const url = new URL(`${n.connectionType}://${n.address}`);\n                    return getIP(url.hostname).then((ip) => {\n                        url.ip = ip || 'none';\n                        return url;\n                    })\n                })).then((urls) => {\n                    const toRemove = urls.filter((url) =>\n                        !this.staticNeighbors.includes(url.hostname) &&\n                        !this.staticNeighbors.includes(url.ip) &&\n                        peers.filter((p) => (\n                            p.data.hostname === url.hostname ||\n                            p.data.ip === url.hostname ||\n                            p.data.hostname === url.ip ||\n                            p.data.ip === url.ip\n                        )).length === 0\n                    );\n                    if (!toRemove.length) {\n                        return resolve(toRemove);\n                    }\n                    this.api.removeNeighbors(toRemove, (err) => {\n                        if (err) {\n                            reject(err);\n                            return;\n                        }\n                        this.log('Removed orphans:'.red, toRemove.map((url) => url.hostname));\n                        resolve(toRemove)\n                    });\n                });\n            });\n        });\n    }\n\n    /**\n     * Updates the list of neighbors at the IRI backend. Removes all neighbors, replacing them with\n     * the newly provided neighbors.\n     * @param {Peer[]} peers\n     * @returns {Promise<Peer[]>}\n     */\n    updateNeighbors (peers) {\n        if (!this.isAvailable()) {\n            return Promise.reject();\n        }\n\n        if (!peers || !peers.length) {\n            return Promise.resolve([]);\n        }\n\n        return new Promise((resolve, reject) => {\n            const addNeighbors = () => {\n                this.addNeighbors(peers).then(resolve).catch(reject);\n            };\n\n            this.api.getNeighbors((error, neighbors) => {\n                if(error) {\n                    reject(error);\n                    return;\n                }\n                Array.isArray(neighbors) && neighbors.length\n                    ? this.api.removeNeighbors(neighbors.map((n) => `${n.connectionType}://${n.address}`), addNeighbors)\n                    : addNeighbors();\n            });\n        });\n    }\n\n    /**\n     * Removes all IRI neighbors, except static neighbors.\n     * @returns {Promise}\n     */\n    removeAllNeighbors () {\n        if (!this.isAvailable()) {\n            return Promise.reject();\n        }\n\n        return new Promise((resolve) => {\n            this.api.getNeighbors((error, neighbors) => {\n                if(error) {\n                    return resolve();\n                }\n                if (Array.isArray(neighbors) && neighbors.length) {\n                    // FIXME: This is broken. staticNeighbors is just a resolved IP. n.address includes port and can be a hostname.\n                    // Hence, the filter will always be true.\n                    const toRemove = neighbors.filter((n) => !this.staticNeighbors.includes(n.address));\n                    return this.api.removeNeighbors(toRemove.map((n) => `${n.connectionType}://${n.address}`), resolve);\n                }\n                resolve();\n            });\n        });\n    }\n\n    /**\n     * Returns IRI node info\n     * @returns {Promise<object>}\n     */\n    getStats () {\n        return new Promise((resolve, reject) => {\n            this.api.getNodeInfo((error, data) => {\n                if(error) {\n                    return reject();\n                }\n                this.iriStats = data;\n                resolve(data);\n            });\n        });\n    }\n\n    /**\n     * Checks if the IRI instance is healthy, and its list of neighbors. Calls back the result to onHealthCheck.\n     * @private\n     */\n    _tick () {\n        const { onHealthCheck } = this.opts;\n        const onError = () => {\n            this.isHealthy = false;\n            onHealthCheck(false);\n        };\n        this.getStats().then(() => {\n            this.api.getNeighbors((error, neighbors) => {\n                if(error) {\n                    return onError();\n                }\n                this.isHealthy = true;\n                // TODO: if the address is IPV6, could that pose a problem?\n                onHealthCheck(true, neighbors.map((n) => ({\n                    address: (new URL(`${n.connectionType}://${n.address}`)).hostname,\n                    numberOfRandomTransactionRequests: n.numberOfRandomTransactionRequests,\n                    numberOfAllTransactions: n.numberOfAllTransactions,\n                    numberOfNewTransactions: n.numberOfNewTransactions,\n                    numberOfInvalidTransactions: n.numberOfInvalidTransactions\n                })));\n            });\n        }).catch(onError);\n    }\n\n    /**\n     * Returns URI for IRI depending on the protocol.\n     * @param {Peer} peer\n     * @returns {string}\n     * @private\n     */\n    _getIRIPeerURI (peer) {\n        return peer.data.IRIProtocol === 'tcp' ? peer.getTCPURI() : peer.getUDPURI();\n    }\n\n}\n\nmodule.exports = {\n    DEFAULT_OPTIONS,\n    IRI\n};\n"
  },
  {
    "path": "src/node/node.js",
    "content": "const WebSocket = require(\"ws\");\nconst ip = require(\"ip\");\nconst pip = require(\"external-ip\")();\nconst weighted = require(\"weighted\");\nconst terminal = require(\"./tools/terminal\");\nconst { Base } = require(\"./base\");\nconst { Heart } = require(\"./heart\");\nconst { Guard } = require(\"./guard\");\nconst { IRI, DEFAULT_OPTIONS: DEFAULT_IRI_OPTIONS } = require(\"./iri\");\nconst {\n    PeerList,\n    DEFAULT_OPTIONS: DEFAULT_LIST_OPTIONS\n} = require(\"./peer-list\");\nconst {\n    getPeerIdentifier,\n    getRandomInt,\n    getSecondsPassed,\n    getVersion,\n    isSameMajorVersion,\n    getIP,\n    createIdentifier\n} = require(\"./tools/utils\");\n\nprocess.on(\"unhandledRejection\", (reason, p) => {\n    console.log(\"Unhandled Rejection at: Promise\", p, \"reason:\", reason);\n});\n\nconst DEFAULT_OPTIONS = {\n    name: \"Deviota Nelson\",\n    cycleInterval: 60,\n    epochInterval: 1200,\n    beatInterval: 10,\n    dataPath: DEFAULT_LIST_OPTIONS.dataPath,\n    port: 16600,\n    IRIHostname: DEFAULT_IRI_OPTIONS.hostname,\n    IRIPort: DEFAULT_IRI_OPTIONS.port,\n    IRIProtocol: \"any\",\n    TCPPort: DEFAULT_IRI_OPTIONS.TCPPort,\n    UDPPort: DEFAULT_IRI_OPTIONS.UDPPort,\n    weightDeflation: 0.95,\n    incomingMax: 6,\n    outgoingMax: 5,\n    maxShareableNodes: 6,\n    localNodes: false,\n    isMaster: false,\n    temporary: false,\n    autoStart: false,\n    logIdent: \"NODE\",\n    neighbors: [],\n    lazyLimit: 300, // Time, after which a peer is considered lazy, if no new TXs received\n    lazyTimesLimit: 3, // starts to penalize peer's quality if connected so many times without new TXs\n    onReady: node => {},\n    onPeerConnected: peer => {},\n    onPeerRemoved: peer => {}\n};\n\n// TODO: add node tests. Need to mock away IRI for this.\nclass Node extends Base {\n    constructor(options) {\n        super({ ...DEFAULT_OPTIONS, ...options });\n        this.opts.logIdent = `${this.opts.port}::NODE`;\n\n        this._onCycle = this._onCycle.bind(this);\n        this._onEpoch = this._onEpoch.bind(this);\n        this._onTick = this._onTick.bind(this);\n        this._onIRIHealth = this._onIRIHealth.bind(this);\n        this._removeNeighbor = this._removeNeighbor.bind(this);\n        this._removeNeighbors = this._removeNeighbors.bind(this);\n        this._addNeighbor = this._addNeighbor.bind(this);\n        this._addNeighbors = this._addNeighbors.bind(this);\n        this.connectPeer = this.connectPeer.bind(this);\n        this.reconnectPeers = this.reconnectPeers.bind(this);\n        this.end = this.end.bind(this);\n\n        this._ready = false;\n        this.sockets = new Map();\n\n        this.opts.autoStart && this.start();\n\n        // Tries to fix the issue #45 https://github.com/SemkoDev/nelson.cli/issues/45\n        // Reasoning: https://github.com/request/request/issues/2161#issuecomment-313375694\n        // Also, cleans up nelson before crashing from the sky.\n        process.on(\"uncaughtException\", err => {\n            if (err.code !== \"ECONNRESET\") {\n                this.end().then(() => {\n                    throw err;\n                });\n            }\n        });\n    }\n\n    /**\n     * Starts the node server, getting public IP, IRI interface, Peer List and Heart.\n     */\n    start() {\n        const {\n            cycleInterval,\n            epochInterval,\n            beatInterval,\n            silent,\n            localNodes\n        } = this.opts;\n        this.guard = new Guard({ beatInterval, silent, localNodes });\n\n        return this._setPublicIP().then(() => {\n            return this._getIRI()\n                .then(iri => {\n                    if (!iri) {\n                        throw new Error(\"IRI could not be started\");\n                    }\n\n                    if (\n                        !iri.staticNeighbors.length &&\n                        this.opts.outgoingMax < DEFAULT_OPTIONS.outgoingMax\n                    ) {\n                        this.log(\n                            `WARNING: you have no static neighbors and outboundMax (${\n                                this.opts.outgoingMax\n                            }) is set below the advised limit (${\n                                DEFAULT_OPTIONS.outgoingMax\n                            })!`\n                        );\n                    }\n\n                    if (this.opts.incomingMax < DEFAULT_OPTIONS.incomingMax) {\n                        this.log(\n                            `WARNING: incomingMax (${\n                                this.opts.incomingMax\n                            }) is set below the advised limit (${\n                                DEFAULT_OPTIONS.incomingMax\n                            })!`\n                        );\n                    }\n\n                    if (this.opts.incomingMax <= DEFAULT_OPTIONS.outgoingMax) {\n                        this.log(\n                            `WARNING: incomingMax (${\n                                this.opts.incomingMax\n                            }) is set below outgoingMax (${\n                                DEFAULT_OPTIONS.outgoingMax\n                            })!`\n                        );\n                    }\n\n                    return this._getList()\n                        .then(() => {\n                            this._createServer();\n\n                            this.heart = new Heart({\n                                silent,\n                                cycleInterval,\n                                epochInterval,\n                                beatInterval,\n                                logIdent: `${this.opts.port}::HEART`,\n                                onCycle: this._onCycle,\n                                onTick: this._onTick,\n                                onEpoch: this._onEpoch\n                            });\n\n                            this._ready = true;\n                            this.opts.onReady(this);\n                            this.heart.start();\n\n                            return this;\n                        })\n                        .catch(err => {\n                            throw err;\n                        });\n                })\n                .catch(err => {\n                    throw err;\n                });\n        });\n    }\n\n    /**\n     * Ends the node, closing HTTP server and IRI backend.\n     * @returns {Promise.<boolean>}\n     */\n    end() {\n        this.log(\"terminating...\");\n\n        this.heart && this.heart.end();\n        this._ready = false;\n\n        const closeServer = () => {\n            return new Promise(resolve => {\n                if (this.server) {\n                    this.server.close();\n                }\n                return this._removeNeighbors(\n                    Array.from(this.sockets.keys())\n                ).then(() => {\n                    this.sockets = new Map();\n                    resolve(true);\n                });\n            });\n        };\n\n        return closeServer().then(() => {\n            return this.iri ? this.iri.end() : true;\n        });\n    }\n\n    /**\n     * Sets a new peer list and returns a list of loaded peers.\n     * @returns {Promise.<Peer[]>}\n     * @private\n     */\n    _getList() {\n        const {\n            localNodes,\n            temporary,\n            silent,\n            neighbors,\n            dataPath,\n            isMaster,\n            lazyLimit,\n            lazyTimesLimit\n        } = this.opts;\n        this.list = new PeerList({\n            multiPort: localNodes,\n            temporary,\n            silent,\n            dataPath,\n            isMaster,\n            lazyLimit,\n            lazyTimesLimit,\n            logIdent: `${this.opts.port}::LIST`\n        });\n\n        return this.list.load(\n            neighbors.filter(n => {\n                const tokens = n.split(\"/\");\n                return !this.isMyself(tokens[0], tokens[1]);\n            })\n        );\n    }\n\n    /**\n     * Sets and returns an IRI instance\n     * @returns {Promise.<IRI>}\n     * @private\n     */\n    _getIRI() {\n        const { IRIHostname, IRIPort, silent } = this.opts;\n\n        return new IRI({\n            logIdent: `${this.opts.port}::IRI`,\n            hostname: IRIHostname,\n            port: IRIPort,\n            onHealthCheck: this._onIRIHealth,\n            silent\n        })\n            .start()\n            .then(iri => {\n                this.iri = iri;\n                return iri;\n            });\n    }\n\n    /**\n     * Tries to get the public IPs of this node.\n     * @private\n     * @returns {Promise}\n     */\n    _setPublicIP() {\n        if (this.opts.localNodes) {\n            return Promise.resolve(0);\n        }\n        return new Promise(resolve => {\n            pip((err, ip) => {\n                if (!err) {\n                    this.ipv4 = ip;\n                    resolve(0);\n                }\n            });\n        });\n    }\n\n    /**\n     * Creates HTTP server for Nelson\n     * @private\n     */\n    _createServer() {\n        this.server = new WebSocket.Server({\n            port: this.opts.port,\n            verifyClient: (info, cb) => {\n                const { req } = info;\n                const deny = () => cb(false, 401);\n                const accept = () => cb(true);\n                this._canConnect(req)\n                    .then(accept)\n                    .catch(deny);\n            }\n        });\n\n        this.server.on(\"connection\", (ws, req) => {\n            this.log(\n                \"incoming connection established\".green,\n                req.connection.remoteAddress\n            );\n            const { remoteAddress: address } = req.connection;\n            const {\n                port,\n                TCPPort,\n                UDPPort,\n                remoteKey,\n                name,\n                protocol\n            } = this._getHeaderIdentifiers(req.headers);\n            const IRIProtocol = this._negotiateProtocol(protocol);\n\n            this.list\n                .add({\n                    hostname: address,\n                    port,\n                    TCPPort,\n                    UDPPort,\n                    remoteKey,\n                    name,\n                    IRIProtocol\n                })\n                .then(peer => {\n                    this._bindWebSocket(ws, peer, true);\n                })\n                .catch(e => {\n                    this.log(\"Error binding/adding\".red, address, port, e);\n                    this.sockets.delete(\n                        Array.from(this.sockets.keys()).find(\n                            p => this.sockets.get(p) === ws\n                        )\n                    );\n                    ws.close();\n                    ws.terminate();\n                });\n        });\n\n        this.server.on(\"headers\", headers => {\n            const myHeaders = this._getHeaders();\n            Object.keys(myHeaders).forEach(key =>\n                headers.push(`${key}: ${myHeaders[key]}`)\n            );\n        });\n        this.server.on(\"error\", function(err) {\n            // basically, do nothing. Most probably a ECONNRESET error.\n            // The peer will be cleaned up on next tick.\n        });\n        this.log(\"server created...\");\n    }\n\n    /**\n     * Resolves promise if the client is allowed to connect, otherwise rejection.\n     * @param {object} req\n     * @returns {Promise}\n     * @private\n     */\n    _canConnect(req) {\n        const { remoteAddress: address } = req.connection;\n        const headers = this._getHeaderIdentifiers(req.headers);\n        const { port, nelsonID, version, remoteKey, protocol } = headers || {};\n        const wrongRequest = !headers;\n\n        return new Promise((resolve, reject) => {\n            if (\n                !this._ready ||\n                !this.guard ||\n                !this.guard.isAllowed(address, port)\n            ) {\n                return reject();\n            }\n\n            if (wrongRequest || !isSameMajorVersion(version)) {\n                this.log(\n                    \"Wrong request or other Nelson version\",\n                    address,\n                    port,\n                    version,\n                    nelsonID,\n                    req.headers\n                );\n                return reject();\n            }\n            if (!this.iri || !this.iri.isHealthy) {\n                this.log(\n                    \"IRI down, denying connections meanwhile\",\n                    address,\n                    port,\n                    nelsonID\n                );\n                return reject();\n            }\n            if (this.isMyself(address, port, nelsonID)) {\n                return reject();\n            }\n            this.list\n                .findByRemoteKeyOrAddress(remoteKey, address, port)\n                .then(peers => {\n                    if (peers.length && this.sockets.get(peers[0])) {\n                        this.log(\"Peer already connected\", address, port);\n                        return reject();\n                    }\n\n                    if (peers.length && this.iri.isStaticNeighbor(peers[0])) {\n                        this.log(\n                            \"Peer is already a static neighbor\",\n                            address,\n                            port\n                        );\n                        return reject();\n                    }\n\n                    // Deny too frequent connections from the same peer.\n                    if (\n                        peers.length &&\n                        this.isSaturationReached() &&\n                        peers[0].data.dateLastConnected &&\n                        getSecondsPassed(peers[0].data.dateLastConnected) <\n                            this.opts.epochInterval * 2\n                    ) {\n                        return reject();\n                    }\n\n                    // Incompatible protocols\n                    if (peers.length[0] && !this._negotiateProtocol(protocol)) {\n                        this.log(\n                            `Couldn't negotiate protocol with ${\n                                peers[0].data.hostname\n                            }: my ${\n                                this.opts.IRIProtocol\n                            } vs remote ${protocol}`.yellow\n                        );\n                        return reject();\n                    }\n\n                    const topCount = parseInt(\n                        Math.sqrt(this.list.all().length) * 2\n                    );\n                    const topPeers = this.list\n                        .getWeighted(300)\n                        .sort((a, b) => b[1] - a[1])\n                        .map(p => p[0])\n                        .slice(0, topCount);\n                    let isTop = false;\n\n                    peers.forEach(p => {\n                        if (topPeers.includes(p)) {\n                            isTop = true;\n                        }\n                    });\n\n                    // The usual way, accept based on personality.\n                    const normalPath = () => {\n                        if (\n                            this._getIncomingSlotsCount() >=\n                            this.opts.incomingMax\n                        ) {\n                            reject();\n                        }\n\n                        // TODO: additional protection measure: make the client solve a computational riddle!\n\n                        this.isAllowed(remoteKey, address, port).then(\n                            allowed => (allowed ? resolve() : reject())\n                        );\n                    };\n\n                    // Accept old, established nodes.\n                    if (isTop) {\n                        if (\n                            this._getIncomingSlotsCount() >=\n                            this.opts.incomingMax\n                        ) {\n                            this._dropRandomNeighbors(1, true).then(resolve);\n                        } else {\n                            resolve();\n                        }\n                    }\n                    // Accept new nodes more easily.\n                    else if (\n                        !peers.length ||\n                        getSecondsPassed(peers[0].data.dateCreated) <=\n                            this.opts.epochInterval * 10\n                    ) {\n                        if (\n                            this._getIncomingSlotsCount() >=\n                            this.opts.incomingMax\n                        ) {\n                            const candidates = Array.from(\n                                this.sockets.keys()\n                            ).filter(\n                                p =>\n                                    getSecondsPassed(p.data.dateCreated) <=\n                                    this.opts.epochInterval * 20\n                            );\n                            if (candidates.length) {\n                                this._dropRandomNeighbors(\n                                    1,\n                                    true,\n                                    candidates\n                                ).then(resolve);\n                            } else {\n                                normalPath();\n                            }\n                        } else {\n                            resolve();\n                        }\n                    } else {\n                        normalPath();\n                    }\n                });\n        });\n    }\n\n    /**\n     * Binds the websocket to the peer and adds callbacks.\n     * @param {WebSocket} ws\n     * @param {Peer} peer\n     * @param {boolean} asServer\n     * @private\n     */\n    _bindWebSocket(ws, peer, asServer = false) {\n        const removeNeighbor = e => {\n            if (!this._ready || !ws || ws.removingNow) {\n                return;\n            }\n            ws.removingNow = true;\n            this._removeNeighbor(peer).then(() => {\n                this.log(\n                    \"connection closed\".red,\n                    this.formatNode(peer.data.hostname, peer.data.port),\n                    `(${e})`\n                );\n            });\n        };\n\n        const onConnected = () => {\n            if (!this._ready) {\n                return;\n            }\n            this.log(\n                \"connection established\".green,\n                this.formatNode(peer.data.hostname, peer.data.port)\n            );\n            this._sendNeighbors(ws);\n            return peer\n                .markConnected()\n                .then(() => this._ready && this.opts.onPeerConnected(peer));\n        };\n\n        ws.isAlive = true;\n        ws.incoming = asServer;\n        this.sockets.set(peer, ws);\n\n        ws.on(\"message\", data =>\n            this._addNeighbors(data, ws.incoming ? 0 : peer.data.weight)\n        );\n        ws.on(\"close\", () => removeNeighbor(\"socket closed\"));\n        ws.on(\"error\", () => removeNeighbor(\"remotely dropped\"));\n        ws.on(\"pong\", () => {\n            ws.isAlive = true;\n        });\n\n        if (asServer) {\n            onConnected().then(\n                () => this._ready && this.iri.addNeighbors([peer])\n            );\n        } else {\n            ws.on(\"upgrade\", res => {\n                // Check for valid headers\n                const head = this._getHeaderIdentifiers(res.headers);\n                if (!head) {\n                    this.log(\"!!\", \"wrong headers received\", head);\n                    return removeNeighbor();\n                }\n                const {\n                    port,\n                    nelsonID,\n                    TCPPort,\n                    UDPPort,\n                    remoteKey,\n                    name,\n                    protocol\n                } = head;\n                const IRIProtocol = this._negotiateProtocol(protocol);\n                this.list\n                    .update(peer, {\n                        port,\n                        nelsonID,\n                        TCPPort,\n                        UDPPort,\n                        remoteKey,\n                        name,\n                        IRIProtocol\n                    })\n                    .then(() => {\n                        if (IRIProtocol) {\n                            this._ready && this.iri.addNeighbors([peer]);\n                        } else {\n                            this.log(\n                                `Couldn't negotiate protocol with ${\n                                    peer.data.hostname\n                                }: my ${\n                                    this.opts.IRIProtocol\n                                } vs remote ${IRIProtocol}`.yellow\n                            );\n                            removeNeighbor();\n                        }\n                    });\n            });\n            ws.on(\"open\", onConnected);\n        }\n    }\n\n    /**\n     * Parses the headers passed between nelson instances\n     * @param {object} headers\n     * @returns {object}\n     * @private\n     */\n    _getHeaderIdentifiers(headers) {\n        const version = headers[\"nelson-version\"];\n        const port = headers[\"nelson-port\"];\n        const nelsonID = headers[\"nelson-id\"];\n        const TCPPort = headers[\"nelson-tcp\"];\n        const UDPPort = headers[\"nelson-udp\"];\n        const remoteKey = headers[\"nelson-key\"];\n        const name = headers[\"nelson-name\"];\n        const protocol = headers[\"nelson-protocol\"] || \"udp\";\n        if (!version || !port || !nelsonID || !TCPPort || !UDPPort) {\n            return null;\n        }\n        return {\n            version,\n            port,\n            nelsonID,\n            TCPPort,\n            UDPPort,\n            remoteKey,\n            name,\n            protocol\n        };\n    }\n\n    /**\n     * Sends list of neighbors through the given socket.\n     * @param {WebSocket} ws\n     * @private\n     */\n    _sendNeighbors(ws) {\n        ws.send(\n            JSON.stringify(\n                this.getPeers().map(p =>\n                    p[0].getHostname().replace(\"/0/\", `/${p[1]}/`)\n                )\n            )\n        );\n    }\n\n    /**\n     * Negotiate protocol to be used between the peers.\n     * If null is returned, the connection cannot be established as there is no consensus.\n     * @param {string} protocol preferred by remote\n     * @param {string} key key for remote\n     * @param {string} remoteKey for this node\n     * @returns {string|null}\n     * @private\n     */\n    _negotiateProtocol(protocol) {\n        if (protocol === \"any\") {\n            switch (this.opts.IRIProtocol) {\n                case \"tcp\":\n                case \"prefertcp\":\n                    return \"tcp\";\n                case \"udp\":\n                case \"preferudp\":\n                case \"any\":\n                default:\n                    return \"udp\";\n            }\n        } else if (protocol === \"tcp\") {\n            switch (this.opts.IRIProtocol) {\n                case \"any\":\n                case \"tcp\":\n                case \"prefertcp\":\n                case \"preferudp\":\n                    return \"tcp\";\n                case \"udp\":\n                default:\n                    return null;\n            }\n        } else if (protocol === \"udp\") {\n            switch (this.opts.IRIProtocol) {\n                case \"any\":\n                case \"udp\":\n                case \"prefertcp\":\n                case \"preferudp\":\n                    return \"udp\";\n                case \"tcp\":\n                default:\n                    return null;\n            }\n        } else if (protocol === \"prefertcp\") {\n            switch (this.opts.IRIProtocol) {\n                case \"any\":\n                case \"tcp\":\n                case \"prefertcp\":\n                    return \"tcp\";\n                case \"preferudp\":\n                case \"udp\":\n                default:\n                    return \"udp\";\n            }\n        } else if (protocol === \"preferudp\") {\n            switch (this.opts.IRIProtocol) {\n                case \"any\":\n                case \"udp\":\n                case \"preferudp\":\n                case \"prefertcp\":\n                    return \"udp\";\n                case \"tcp\":\n                default:\n                    return \"tcp\";\n            }\n        }\n    }\n\n    /**\n     * Adds a neighbor to known neighbors list.\n     * @param {string} neighbor\n     * @param {number} weight of the neighbor to assign\n     * @returns {Promise}\n     * @private\n     */\n    _addNeighbor(neighbor, weight) {\n        // this.log('adding neighbor', neighbor);\n        const tokens = neighbor.split(\"/\");\n        if (\n            !isFinite(tokens[1]) ||\n            !isFinite(tokens[2]) ||\n            !isFinite(tokens[3])\n        ) {\n            return Promise.resolve(null);\n        }\n        return this.isMyself(tokens[0], tokens[1])\n            ? Promise.resolve(null)\n            : this.list.add({\n                  hostname: tokens[0],\n                  port: tokens[1],\n                  TCPPort: tokens[2],\n                  UDPPort: tokens[3],\n                  peerWeight: weight,\n                  weight:\n                      weight *\n                      parseFloat(tokens[4] || 0) *\n                      this.opts.weightDeflation,\n                  IRIProtocol: tokens[5] || \"udp\"\n              });\n    }\n\n    /**\n     * Parses raw data from peer's response and adds the provided neighbors.\n     * @param {string} data raw from peer's response\n     * @param {number} weight to assign to the parsed neighbors.\n     * @returns {Promise}\n     * @private\n     */\n    _addNeighbors(data, weight) {\n        // this.log('add neighbors', data);\n        return new Promise((resolve, reject) => {\n            try {\n                Promise.all(\n                    JSON.parse(data)\n                        .slice(0, this.opts.maxShareableNodes)\n                        .map(neighbor => this._addNeighbor(neighbor, weight))\n                ).then(resolve);\n            } catch (e) {\n                reject(e);\n            }\n        });\n    }\n\n    /**\n     * Returns Nelson headers for request/response purposes\n     * @param {string} key of the peer\n     * @returns {Object}\n     * @private\n     */\n    _getHeaders(key = \"\") {\n        return {\n            \"Content-Type\": \"application/json\",\n            \"Nelson-Version\": getVersion(),\n            \"Nelson-Port\": `${this.opts.port}`,\n            \"Nelson-ID\": this.heart.personality.publicId,\n            \"Nelson-TCP\": this.opts.TCPPort,\n            \"Nelson-UDP\": this.opts.UDPPort,\n            \"Nelson-Key\": key,\n            \"Nelson-Name\": this.opts.name,\n            \"Nelson-Protocol\": this.opts.IRIProtocol\n        };\n    }\n\n    /**\n     * Returns amount of incoming connections\n     * @returns {Number}\n     * @private\n     */\n    _getIncomingSlotsCount() {\n        const arr = Array.from(this.sockets.values()).filter(\n            ws => ws.readyState < 2\n        );\n        return arr.filter(ws => ws.incoming).length;\n    }\n\n    /**\n     * Returns amount of outgoing connections\n     * @returns {Number}\n     * @private\n     */\n    _getOutgoingSlotsCount() {\n        const arr = Array.from(this.sockets.values()).filter(\n            ws => ws.readyState < 2\n        );\n        return arr.filter(ws => !ws.incoming).length;\n    }\n\n    /**\n     * Disconnects a peer.\n     * @param {Peer} peer\n     * @returns {Promise<Peer>}\n     * @private\n     */\n    _removeNeighbor(peer) {\n        if (!this._ready || !this.sockets.get(peer)) {\n            return Promise.resolve([]);\n        }\n        // this.log('removing neighbor', this.formatNode(peer.data.hostname, peer.data.port));\n        return this._removeNeighbors([peer]);\n    }\n\n    /**\n     * Disconnects several peers.\n     * @param {Peer[]} peers\n     * @returns {Promise<Peer[]>}\n     * @private\n     */\n    _removeNeighbors(peers) {\n        // this.log('removing neighbors');\n\n        const doRemove = () => {\n            return Promise.all(\n                peers.map(\n                    peer =>\n                        new Promise(resolve => {\n                            const ws = this.sockets.get(peer);\n                            if (ws) {\n                                ws.close();\n                                ws.terminate();\n                            }\n                            this.sockets.delete(peer);\n                            peer.markDisconnected().then(() => {\n                                this.opts.onPeerRemoved(peer);\n                                resolve(peer);\n                            });\n                        })\n                )\n            );\n        };\n\n        if (!this.iri || !this.iri.isHealthy) {\n            return Promise.resolve(doRemove());\n        }\n\n        return this.iri\n            .removeNeighbors(peers)\n            .then(doRemove)\n            .catch(doRemove);\n    }\n\n    /**\n     * Randomly removes a given amount of peers from current connections.\n     * Low-quality peers are favored to be removed.\n     * @param {number} amount\n     * @param {boolean} incomingOnly - only drop incoming connections\n     * @param {Peer[]} array - array of connected peers to use for dropping\n     * @returns {Promise.<Peer[]>} removed peers\n     * @private\n     */\n    _dropRandomNeighbors(amount = 1, incomingOnly = false, array = null) {\n        const peers = array\n            ? array\n            : incomingOnly\n                ? Array.from(this.sockets.keys()).filter(\n                      p => this.sockets.get(p).incoming\n                  )\n                : array\n                    ? array\n                    : Array.from(this.sockets.keys());\n        const selectRandomPeer = () => {\n            const weights = peers.map(p =>\n                Math.max(p.getPeerQuality(), 0.0001)\n            );\n            return weighted(peers, weights);\n        };\n        const toRemove = [];\n\n        if (!peers.length) {\n            return Promise.resolve([]);\n        }\n\n        for (let x = 0; x < amount; x++) {\n            const peer = selectRandomPeer();\n            peers.splice(peers.indexOf(peer), 1);\n            toRemove.push(peer);\n        }\n\n        return this._removeNeighbors(toRemove);\n    }\n\n    /**\n     * Connects to a peer, checking if it's online and trying to get its peers.\n     * @param {Peer} peer\n     * @returns {Peer}\n     */\n    connectPeer(peer) {\n        this.log(\n            \"connecting peer\".yellow,\n            this.formatNode(peer.data.hostname, peer.data.port)\n        );\n        const key = peer.data.key || createIdentifier();\n        this.list.update(peer, {\n            dateTried: new Date(),\n            tried: (peer.data.tried || 0) + 1,\n            key\n        });\n        this._bindWebSocket(\n            new WebSocket(peer.getNelsonWebsocketURI(), {\n                headers: this._getHeaders(key),\n                handshakeTimeout: 5000\n            }),\n            peer\n        );\n        return peer;\n    }\n\n    /**\n     * Connects the node to a new set of random addresses that comply with the out/in rules.\n     * Up to a soft maximum.\n     * @returns {Peer[]} List of new connected peers\n     */\n    reconnectPeers() {\n        // TODO: remove old peers by inverse weight, maybe? Not urgent. Can be added at a later point.\n        // this.log('reconnectPeers');\n        // If max was reached, do nothing:\n        const toTry = this.opts.outgoingMax - this._getOutgoingSlotsCount();\n\n        if (\n            !this.iri ||\n            !this.iri.isHealthy ||\n            toTry < 1 ||\n            this.isMaster ||\n            this._getOutgoingSlotsCount() >= this.opts.outgoingMax\n        ) {\n            return [];\n        }\n\n        // Get connectable peers:\n        const list = this.list\n            .all()\n            .filter(\n                p =>\n                    !p.data.dateTried ||\n                    getSecondsPassed(p.data.dateTried) >\n                        this.opts.beatInterval *\n                            Math.max(2, 2 * p.data.tried || 0)\n            )\n            .filter(p => !this.iri.isStaticNeighbor(p));\n\n        // Get allowed peers:\n        return this.list\n            .getWeighted(192, list)\n            .filter(p => !this.sockets.get(p[0]))\n            .slice(0, toTry)\n            .map(p => this.connectPeer(p[0]));\n    }\n\n    /**\n     * Returns a set of peers ready to be shared with their respective weight ratios.\n     * @returns {Array[]}\n     */\n    getPeers() {\n        // The node tries to recommend best of the best, even better nodes than it tries to connect, usually.\n        // One tries to be helpful to the others, remember? Only suggesting top-notch peers.\n        return this.list.getWeighted(this.opts.maxShareableNodes, null, 2);\n    }\n\n    /**\n     * Each epoch, disconnect all peers and reconnect new ones.\n     * @private\n     */\n    _onEpoch() {\n        this.log(\"new epoch and new id:\", this.heart.personality.id);\n        if (!this.isSaturationReached()) {\n            return Promise.resolve(false);\n        }\n        // Master node should recycle all its connections\n        if (this.opts.isMaster) {\n            return this._removeNeighbors(Array.from(this.sockets.keys())).then(\n                () => {\n                    this.reconnectPeers();\n                    return false;\n                }\n            );\n        }\n        return this._dropRandomNeighbors(\n            getRandomInt(0, this._getOutgoingSlotsCount())\n        ).then(() => {\n            this.reconnectPeers();\n            return false;\n        });\n    }\n\n    /**\n     * Checks whether expired peers are still connectable.\n     * If not, disconnect/remove them.\n     * @private\n     */\n    _onCycle() {\n        this.log(\"new cycle\");\n        const promises = [];\n        // Remove closed or dead sockets. Otherwise set as not alive and ping:\n        this.sockets.forEach((ws, peer) => {\n            if (ws.readyState > 1 || !ws.isAlive) {\n                promises.push(this._removeNeighbor(peer));\n            } else if (peer.isLazy()) {\n                this.log(\n                    `Peer ${peer.data.hostname} (${\n                        peer.data.name\n                    }) is lazy for more than ${\n                        this.opts.lazyLimit\n                    } seconds. Removing...!`.yellow\n                );\n                promises.push(this._removeNeighbor(peer));\n            } else if (ws.readyState === 1) {\n                ws.isAlive = false;\n                ws.ping(\"\", false);\n            }\n        });\n        return Promise.all(promises).then(() => false);\n    }\n\n    /**\n     * Try connecting to more peers.\n     * @returns {Promise}\n     * @private\n     */\n    _onTick() {\n        terminal.nodes({\n            nodes: this.list.all(),\n            connected: Array.from(this.sockets.keys())\n                .filter(p => this.sockets.get(p).readyState === 1)\n                .map(p => p.data)\n        });\n\n        // Try connecting more peers. Master nodes do not actively connect (no outgoing connections).\n        if (\n            !this.opts.isMaster &&\n            this._getOutgoingSlotsCount() < this.opts.outgoingMax\n        ) {\n            return new Promise(resolve => {\n                this.reconnectPeers();\n                resolve(false);\n            });\n        }\n\n        // If for some reason the maximal nodes were overstepped, drop one.\n        else if (this._getIncomingSlotsCount() > this.opts.incomingMax) {\n            return this._dropRandomNeighbors(\n                this._getIncomingSlotsCount() - this.opts.incomingMax,\n                true\n            ).then(() => false);\n        } else {\n            return Promise.resolve(false);\n        }\n    }\n\n    /**\n     * Callback for IRI to check for health and neighbors.\n     * If unhealthy, disconnect all. Otherwise, disconnect peers that are not in IRI list any more for any reason.\n     * @param {boolean} healthy\n     * @param {object[]} data\n     * @private\n     */\n    _onIRIHealth(healthy, data) {\n        if (!healthy) {\n            // Do not drop connections, yet. IRI might just be unavailable for a moment.\n            // If it still has \"old\" neighbors, they will leak, causing more nodes to be added than permitted.\n            this.log(\"IRI gone...\".red);\n            return;\n            // return this._removeNeighbors(Array.from(this.sockets.keys()));\n        }\n        Promise.all(data.map(n => n.address).map(getIP))\n            .then(neighbors => {\n                const toRemove = [];\n                Array.from(this.sockets.keys())\n                    // It might be that the neighbour was just added and not yet included in IRI...\n                    .filter(p => getSecondsPassed(p.data.dateLastConnected) > 5)\n                    .forEach(peer => {\n                        if (\n                            !neighbors.includes(peer.data.hostname) &&\n                            (!peer.data.ip ||\n                                (peer.data.ip &&\n                                    !neighbors.includes(peer.data.ip)))\n                        ) {\n                            toRemove.push(peer);\n                        } else {\n                            const index = Math.max(\n                                neighbors.indexOf(peer.data.hostname),\n                                neighbors.indexOf(peer.data.ip)\n                            );\n                            index >= 0 && peer.updateConnection(data[index]);\n                        }\n                    });\n                if (toRemove.length) {\n                    this.log(\n                        \"Disconnecting Nelson nodes that are missing in IRI:\"\n                            .red,\n                        toRemove.map(p => p.data.hostname)\n                    );\n                    return this._removeNeighbors(toRemove);\n                }\n            })\n            .then(() =>\n                this.iri.cleanupNeighbors(Array.from(this.sockets.keys()))\n            );\n    }\n\n    /**\n     * Returns whether the provided address/port/id matches this node\n     * @param {string} address\n     * @param {number|string} port\n     * @param {string|null} nelsonID\n     * @returns {boolean}\n     */\n    isMyself(address, port, nelsonID = null) {\n        const isPrivate =\n            ip.isPrivate(address) ||\n            [\"127.0.0.1\", \"localhost\"].includes(address);\n        const sameAddress = isPrivate || address === this.ipv4;\n        const samePort = parseInt(port) === this.opts.port;\n        const sameID =\n            this.heart &&\n            this.heart.personality &&\n            nelsonID === this.heart.personality.publicId;\n        return sameID || (sameAddress && (!this.opts.localNodes || samePort));\n    }\n\n    /**\n     * Returns whether certain address can contact this instance.\n     * @param {string} remoteKey\n     * @param {string} address\n     * @param {number} port\n     * @param {boolean} checkTrust - whether to check for trusted peer\n     * @param {number} easiness - how \"easy\" it is to get in\n     * @returns {Promise<boolean>}\n     */\n    isAllowed(remoteKey, address, port, checkTrust = true, easiness = 24) {\n        const allowed = () =>\n            getPeerIdentifier(\n                `${this.heart.personality.id}:${\n                    this.opts.localNodes ? port : address\n                }`\n            )\n                .slice(0, this._getMinEasiness(easiness))\n                .indexOf(this.heart.personality.feature) >= 0;\n\n        return checkTrust\n            ? this.list\n                  .findByRemoteKeyOrAddress(remoteKey, address, port)\n                  .then(ps => ps.filter(p => p.isTrusted()).length || allowed())\n            : Promise.resolve(allowed());\n    }\n\n    /**\n     * Returns whether the amount of connected nodes has reached a certain threshold.\n     * @returns {boolean}\n     */\n    isSaturationReached() {\n        const ratioConnected =\n            (this._getOutgoingSlotsCount() + this._getIncomingSlotsCount()) /\n            (this.opts.outgoingMax + this.opts.incomingMax);\n        return ratioConnected >= 0.75;\n    }\n\n    /**\n     * For new nodes, make it easy to find nodes and contact them\n     * @param {number} easiness - how easy it is to get in/out\n     * @returns {number} updated easiness value\n     * @private\n     */\n    _getMinEasiness(easiness) {\n        // New nodes are trusting less the incoming connections.\n        // As the node matures in the community, it becomes more welcoming for inbound requests.\n        const l = this.list.all().filter(p => p.data.connected).length;\n        return Math.min(easiness, Math.max(5, parseInt(l / 2)));\n    }\n}\n\nmodule.exports = {\n    DEFAULT_OPTIONS,\n    Node\n};\n"
  },
  {
    "path": "src/node/peer-list.js",
    "content": "const path = require(\"path\");\nconst ip = require(\"ip\");\nconst dns = require(\"dns\");\nconst tmp = require(\"tmp\");\nconst { URL } = require(\"url\");\nconst weighted = require(\"weighted\");\nconst Datastore = require(\"nedb\");\nconst { Base } = require(\"./base\");\nconst { Peer } = require(\"./peer\");\nconst { DEFAULT_OPTIONS: DEFAULT_IRI_OPTIONS } = require(\"./iri\");\nconst { getSecondsPassed, createIdentifier } = require(\"./tools/utils\");\n\nconst DEFAULT_OPTIONS = {\n    dataPath: path.join(process.cwd(), \"data/neighbors.db\"),\n    isMaster: false,\n    multiPort: false,\n    temporary: false,\n    logIdent: \"LIST\",\n    ageNormalizer: 3600,\n    lazyLimit: 300, // Time, after which a peer is considered lazy, if no new TXs received\n    lazyTimesLimit: 3 // starts to penalize peer's quality if connected so many times without new TXs\n};\n\n/**\n * A class that manages a list of peers and its persistence in the database\n * @class PeerList\n */\nclass PeerList extends Base {\n    constructor(options) {\n        super({ ...DEFAULT_OPTIONS, ...options });\n        this.onPeerUpdate = this.onPeerUpdate.bind(this);\n        this.loaded = false;\n        this.peers = [];\n\n        this.db = new Datastore({\n            filename: this.opts.temporary\n                ? tmp.tmpNameSync()\n                : this.opts.dataPath,\n            autoload: true\n        });\n        this.db.persistence.setAutocompactionInterval(30000);\n    }\n\n    /**\n     * Loads the peer database, preloading defaults, if any.\n     * @param {string[]} defaultPeerURLs\n     * @returns {Promise<Peer>}\n     */\n    load(defaultPeerURLs) {\n        return new Promise(resolve => {\n            this.db.find({}, (err, docs) => {\n                this.peers = docs.map(\n                    data => new Peer(data, this._getPeerOptions())\n                );\n                this.loadDefaults(defaultPeerURLs).then(() => {\n                    this.log(\"DB and default peers loaded\");\n                    this.loaded = true;\n                    resolve(this.peers);\n                });\n            });\n        });\n    }\n\n    /**\n     * Adds default peers to the database/list.\n     * @param {string[]} defaultPeerURLs\n     * @returns {Promise<Peer>}\n     */\n    loadDefaults(defaultPeerURLs = []) {\n        return Promise.all(\n            defaultPeerURLs.map(uri => {\n                const tokens = uri.split(\"/\");\n                return this.add({\n                    hostname: tokens[0],\n                    port: tokens[1],\n                    TCPPort: tokens[2],\n                    UDPPort: tokens[3],\n                    weight: tokens[4] || 1.0,\n                    IRIProtocol: tokens[5] || \"udp\",\n                    isTrusted: true\n                });\n            })\n        );\n    }\n\n    /**\n     * Update callback when the peer's data has been changed from within the peer.\n     * @param peer\n     * @returns {Promise.<Peer>}\n     */\n    onPeerUpdate(peer) {\n        const data = { ...peer.data };\n        delete data._id;\n        return this.update(peer, data, false);\n    }\n\n    /**\n     * Partially updates a peer with the provided data. Saves into database.\n     * @param {Peer} peer\n     * @param {Object} data\n     * @param {boolean} refreshPeer - whether to update the peers data.\n     * @returns {Promise<Peer>}\n     */\n    update(peer, data, refreshPeer = true) {\n        const newData = { ...peer.data, ...data };\n        return new Promise(resolve => {\n            this.db.update(\n                { _id: peer.data._id },\n                newData,\n                { returnUpdatedDocs: true },\n                () => {\n                    // this.log(`updated peer ${peer.data.hostname}:${peer.data.port}`, JSON.stringify(data));\n                    refreshPeer\n                        ? peer.update(newData, false).then(() => resolve(peer))\n                        : resolve(peer);\n                }\n            );\n        });\n    }\n\n    /**\n     * Returns currently loaded peers.\n     * @returns {Peer[]}\n     */\n    all() {\n        return this.peers;\n    }\n\n    /**\n     * Removes all peers.\n     */\n    clear() {\n        this.log(\"Clearing all known peers\");\n        this.peers = [];\n        return new Promise(resolve =>\n            this.db.remove({}, { multi: true }, resolve)\n        );\n    }\n\n    /**\n     * Gets the average age of all known peers\n     * @returns {number}\n     */\n    getAverageAge() {\n        return (\n            this.peers\n                .map(p => getSecondsPassed(p.data.dateCreated))\n                .reduce((s, x) => s + x, 0) / this.peers.length\n        );\n    }\n\n    /**\n     * Returns peers, whose remoteKey, hostname or IP equals the address.\n     * Port is only considered if multiPort option is true.\n     * If the address/port matches, the remoteKey is not considered.\n     * @param {string} remoteKey\n     * @param {string} address\n     * @param {number} port\n     * @returns {Promise<Peer[]|null>}\n     */\n    findByRemoteKeyOrAddress(remoteKey, address, port) {\n        return new Promise(resolve => {\n            this.findByAddress(address, port).then(peers => {\n                if (peers.length) {\n                    return resolve(peers);\n                }\n                resolve(\n                    this.peers.filter(\n                        p => p.data.remoteKey && p.data.remoteKey === remoteKey\n                    )\n                );\n            });\n        });\n    }\n\n    /**\n     * Returns peers, whose hostname or IP equals the address.\n     * Port is only considered if mutiPort option is true.\n     * @param {string} address\n     * @param {number} port\n     * @returns {Promise<Peer[]|null>}\n     */\n    findByAddress(address, port) {\n        const addr = PeerList.cleanAddress(address);\n        return new Promise(resolve => {\n            const findWithIP = ip => {\n                const peers = this.peers.filter(\n                    p =>\n                        p.data.hostname === addr ||\n                        p.data.hostname === address ||\n                        (ip && (p.data.hostname === ip || p.data.ip === ip))\n                );\n                resolve(\n                    this.opts.multiPort\n                        ? peers.filter(p => p.data.port == port)\n                        : peers\n                );\n            };\n\n            if (\n                ip.isV6Format(addr) ||\n                ip.isV4Format(addr) ||\n                this.opts.multiPort\n            ) {\n                findWithIP(addr);\n            } else {\n                dns.resolve(addr, \"A\", (error, results) =>\n                    findWithIP(error || !results.length ? null : results[0])\n                );\n            }\n        });\n    }\n\n    /**\n     * Calculates the trust score of a peer\n     * @param {Peer} peer\n     * @returns {number}\n     */\n    getPeerTrust(peer) {\n        const age =\n            parseFloat(getSecondsPassed(peer.data.dateCreated)) /\n            this.opts.ageNormalizer;\n        if (this.opts.isMaster) {\n            const weightedAge =\n                (peer.data.connected || peer.isTrusted() ? age : 0) ** 2 *\n                peer.getPeerQuality() ** 2;\n            return Math.max(weightedAge, 0.0001);\n        }\n        const weightedAge =\n            age ** 2 *\n            peer.getPeerQuality() ** 2 *\n            (1.0 + peer.data.weight * 10) ** 2;\n        return Math.max(weightedAge, 0.0001);\n    }\n\n    /**\n     * Get a certain amount of weighted random peers. Return peers with their respective weight ratios\n     * The weight depends on relationship age (connections) and trust (weight).\n     * @param {number} amount\n     * @param {Peer[]} sourcePeers list of peers to use. Optional for filtering purposes.\n     * @param {number} power by which increase the weights\n     * @returns {Array<Peer, number>}\n     */\n    getWeighted(amount = 0, sourcePeers = null, power = 1.0) {\n        amount = amount || this.peers.length;\n        const peers = sourcePeers || Array.from(this.peers);\n        if (!peers.length) {\n            return [];\n        }\n        const allWeights = peers.map(p => this.getPeerTrust(p) ** power);\n        const weightsMax = Math.max(...allWeights);\n\n        const choices = [];\n        const getChoice = () => {\n            const peer = weighted(peers, allWeights);\n            const index = peers.indexOf(peer);\n            const weight = allWeights[index];\n            peers.splice(index, 1);\n            allWeights.splice(index, 1);\n            choices.push([peer, weight / weightsMax]);\n        };\n\n        for (let x = 0; x < amount; x++) {\n            if (peers.length < 1) {\n                break;\n            }\n            getChoice();\n        }\n        return choices\n            .filter(c => c && c[0])\n            .map(c => [c[0], c[0].isTrusted() ? 1.0 : c[1]]);\n    }\n\n    /**\n     * Adds a new peer to the list using an URI\n     * @param {object} data\n     * @returns {*}\n     */\n    add(data) {\n        const {\n            hostname,\n            port: rawPort,\n            TCPPort: rawTCPPort,\n            UDPPort: rawUDPPort,\n            IRIProtocol,\n            isTrusted,\n            peerWeight,\n            weight,\n            remoteKey,\n            name\n        } = Object.assign(\n            {\n                TCPPort: DEFAULT_IRI_OPTIONS.TCPPort,\n                UDPPort: DEFAULT_IRI_OPTIONS.UDPPort,\n                IRIProtocol: \"udp\",\n                isTrusted: false,\n                peerWeight: 0.5,\n                weight: 0,\n                remoteKey: null\n            },\n            data\n        );\n        const port = parseInt(rawPort);\n        const TCPPort = parseInt(rawTCPPort || DEFAULT_IRI_OPTIONS.TCPPort);\n        const UDPPort = parseInt(rawUDPPort || DEFAULT_IRI_OPTIONS.UDPPort);\n\n        return this.findByRemoteKeyOrAddress(remoteKey, hostname, port).then(\n            peers => {\n                const addr = PeerList.cleanAddress(hostname);\n                const existing = peers.length && peers[0];\n\n                if (existing) {\n                    return this.update(existing, {\n                        weight: weight\n                            ? existing.data.weight\n                                ? weight * peerWeight +\n                                  existing.data.weight * (1.0 - peerWeight)\n                                : weight\n                            : existing.data.weight,\n                        key: existing.data.key || createIdentifier(),\n                        remoteKey: remoteKey || existing.data.remoteKey,\n                        name: name || existing.data.name,\n                        hostname: addr,\n                        port,\n                        TCPPort,\n                        UDPPort,\n                        IRIProtocol\n                    });\n                } else {\n                    this.log(\n                        `Adding to the list of known Nelson peers: ${hostname}:${port}`\n                    );\n                    const peerIP =\n                        ip.isV4Format(addr) || ip.isV6Format(addr)\n                            ? addr\n                            : null;\n                    const peer = new Peer(\n                        {\n                            port,\n                            hostname: addr,\n                            ip: peerIP,\n                            TCPPort: TCPPort || DEFAULT_IRI_OPTIONS.TCPPort,\n                            UDPPort: UDPPort || DEFAULT_IRI_OPTIONS.UDPPort,\n                            IRIProtocol: IRIProtocol || \"udp\",\n                            isTrusted,\n                            name,\n                            weight,\n                            remoteKey,\n                            key: createIdentifier(),\n                            dateCreated: new Date()\n                        },\n                        this._getPeerOptions()\n                    );\n                    this.peers.push(peer);\n                    return new Promise((resolve, reject) => {\n                        this.db.insert(peer.data, (err, doc) => {\n                            if (err) {\n                                reject(err);\n                            }\n                            peer.update(doc);\n                            resolve(peer);\n                        });\n                    });\n                }\n            }\n        );\n    }\n\n    _getPeerOptions() {\n        const { lazyLimit, lazyTimesLimit } = this.opts;\n        return { lazyLimit, lazyTimesLimit, onDataUpdate: this.onPeerUpdate };\n    }\n\n    /**\n     * Converts an address to a cleaner format.\n     * @param {string} address\n     * @returns {string}\n     */\n    static cleanAddress(address) {\n        if (!ip.isV4Format(address) && !ip.isV6Format(address)) {\n            return address;\n        }\n        return ip.isPrivate(address)\n            ? \"localhost\"\n            : address.replace(\"::ffff:\", \"\");\n    }\n}\n\nmodule.exports = {\n    DEFAULT_OPTIONS,\n    PeerList\n};\n"
  },
  {
    "path": "src/node/peer.js",
    "content": "const ip = require('ip');\nconst dns = require('dns');\nconst { Base } = require('./base');\nconst { DEFAULT_OPTIONS: DEFAULT_IRI_OPTIONS } = require('./iri');\nconst { getSecondsPassed, createIdentifier } = require('./tools/utils');\n\nconst PROTOCOLS = ['tcp', 'udp', 'prefertcp', 'preferudp', 'any'];\nconst DEFAULT_OPTIONS = {\n    onDataUpdate: (data) => Promise.resolve(),\n    ipRefreshTimeout: 1200,\n    silent: true,\n    logIdent: 'PEER',\n    lazyLimit: 300, // Time, after which a peer is considered lazy, if no new TXs received\n    lazyTimesLimit: 3 // starts to penalize peer's quality if connected so many times without new TXs\n};\nconst DEFAULT_PEER_DATA = {\n    name: null,\n    hostname: null,\n    ip: null,\n    port: 31337,\n    TCPPort: DEFAULT_IRI_OPTIONS.TCPPort,\n    UDPPort: DEFAULT_IRI_OPTIONS.UDPPort,\n    IRIProtocol: 'udp', // Assume all old Nelsons to be running udp.\n    seen: 1,\n    connected: 0,\n    tried: 0,\n    weight: 0,\n    dateTried: null,\n    dateLastConnected: null,\n    dateCreated: null,\n    isTrusted: false,\n    key: null,\n    remoteKey: null,\n    lastConnections: []\n};\n\n/**\n * A utility class for a peer that holds peer's data and provides a few util methods.\n *\n * @class Peer\n */\nclass Peer extends Base {\n    constructor (data = {}, options) {\n        super({ ...DEFAULT_OPTIONS, ...options, logIdent: `${data.hostname}:${data.port}`});\n        this.data = null;\n        this.lastConnection = null;\n        // Make sure to migrate database if anything else is added to the defaults...\n        this.update({ ...DEFAULT_PEER_DATA, ...data });\n    }\n\n    /**\n     * Partial peer's data update\n     * @param {Object} data\n     * @param {boolean} doCallback - whether to call back on data changes\n     * @returns {Promise<Object>} - updates data\n     */\n    update (data, doCallback=true) {\n        // Reset last updated date if the hostname has changed\n        let shouldUpdate = false;\n        const hostnameChanged = this.data && (this.data.hostname !== (data && data.hostname));\n        this.iplastUpdated = this.data && hostnameChanged ? null : this.iplastUpdated;\n        this.data = { ...this.data, ...data };\n        if (hostnameChanged && this.data.ip) {\n            this.data.ip = null;\n            shouldUpdate = true;\n        }\n        if (!this.data.ip) {\n            this.data.ip = this._isHostnameIP() ? this.data.hostname : null;\n            shouldUpdate = true;\n        }\n        return shouldUpdate && doCallback\n            ? this.opts.onDataUpdate(this).then(() => this.data)\n            : Promise.resolve(this.data);\n    }\n\n    /**\n     * Gets the IP address of the peer. Independently if peer's address is a hostname or IP.\n     * Update's the peer data to save the last known IP.\n     * @returns {Promise<string>}\n     */\n    getIP () {\n        return new Promise ((resolve) => {\n            if (!this._hasCorrectIP() || (!this._isHostnameIP() && this._isIPOutdated())) {\n                dns.resolve(this.data.hostname, 'A', (error, results) => {\n                    // if there was an error, we set the hostname as ip, even if it's not the case.\n                    // It will be re-tried next refresh cycle.\n                    const ip = error || !results.length ? null : results[0];\n                    this.iplastUpdated = new Date();\n                    if (ip && ip !== this.data.ip) {\n                        this.data.ip = ip;\n                        this.opts.onDataUpdate(this).then(() => resolve(ip));\n                    } else {\n                        resolve(ip)\n                    }\n                })\n            }\n            else {\n                resolve(this.data.ip)\n            }\n        })\n    }\n\n    /**\n     * Marks this node as connected.\n     * @returns {Promise.<Peer>}\n     */\n    markConnected () {\n        if (this.lastConnection) {\n            return Promise.resolve(this);\n        }\n        this.lastConnection = {\n            start: new Date(),\n            duration: 0,\n            numberOfAllTransactions: 0,\n            numberOfNewTransactions: 0,\n            numberOfInvalidTransactions: 0\n        };\n\n        return this.update({\n            key: this.data.key || createIdentifier(),\n            tried: 0,\n            connected: this.data.connected + 1,\n            dateLastConnected: new Date()\n        }).then(() => this);\n    }\n\n    /**\n     * Marks the node as disconnected. Saves connection stats in DB.\n     * @returns {Promise.<Peer>}\n     */\n    markDisconnected () {\n        if (!this.lastConnection) {\n            return Promise.resolve(this);\n        }\n\n        const lastConnections = [ ...this.data.lastConnections, {\n            ...this.lastConnection,\n            end: new Date(),\n            duration: this.getConnectionDuration()\n        }].slice(-10);\n\n        this.lastConnection = null;\n        return this.update({ lastConnections }).then(() => this);\n    }\n\n    /**\n     * Returns time in seconds that passed since the node has been connected\n     * @returns {number}\n     */\n    getConnectionDuration () {\n        if (!this.lastConnection) {\n            return 0;\n        }\n        return getSecondsPassed(this.lastConnection.start)\n    }\n\n    /**\n     * Updates the stats of the currently connected peer\n     * @param data\n     */\n    updateConnection (data) {\n        if (!this.lastConnection) {\n            return;\n        }\n\n        const {\n            numberOfAllTransactions,\n            numberOfRandomTransactionRequests,\n            numberOfNewTransactions,\n            numberOfInvalidTransactions\n        } = data;\n        this.lastConnection = {\n            ...this.lastConnection,\n            numberOfAllTransactions,\n            numberOfRandomTransactionRequests,\n            numberOfNewTransactions,\n            numberOfInvalidTransactions\n        }\n    }\n\n    /**\n     * Returns peer's quality based on last connection stats.\n     * @returns {number}\n     */\n    getPeerQuality () {\n        const history = [ ...this.data.lastConnections, this.lastConnection].filter(h => h);\n        const newTrans = history.reduce((s, h) => s + h.numberOfNewTransactions, 0);\n        const badTrans = history.reduce((s, h) => s + h.numberOfInvalidTransactions, 0);\n        const rndTrans = history.reduce((s, h) => s + (h.numberOfRandomTransactionRequests || 0), 0);\n        const badRatio = parseFloat(badTrans * 5 + rndTrans) / (newTrans || 1);\n        const serialPenalization = !this.isTrusted() && !newTrans && history.length >= this.opts.lazyTimesLimit\n            ? 1.0 / history.length\n            : 1.0;\n        const score = Math.max(0.0, 1.0 / (badRatio || 1)) * serialPenalization;\n        return Math.max(0.01, score);\n    }\n\n    /**\n     * Returns whether a connected peer has not sent any new transactions for a prolonged period of time.\n     * @returns {boolean}\n     */\n    isLazy () {\n        return this.lastConnection &&\n            getSecondsPassed(this.lastConnection.start) > this.opts.lazyLimit &&\n            (\n                this.lastConnection.numberOfNewTransactions === 0 ||\n                this.lastConnection.numberOfNewTransactions < this.lastConnection.numberOfRandomTransactionRequests\n            )\n    }\n\n    getTCPURI () {\n        return `tcp://${this._getIPString(this.data.hostname)}:${this.data.TCPPort}`\n    }\n\n    getUDPURI () {\n        return `udp://${this._getIPString(this.data.hostname)}:${this.data.UDPPort}`\n    }\n\n    getNelsonURI () {\n        return `http://${this._getIPString(this.data.hostname)}:${this.data.port}`\n    }\n\n    getNelsonWebsocketURI () {\n        return `ws://${this._getIPString(this.data.hostname)}:${this.data.port}`\n    }\n\n    getHostname () {\n        return `${this.data.hostname}/${this.data.port}/${this.data.TCPPort}/${this.data.UDPPort}/0/${this.data.IRIProtocol}`\n    }\n\n    isTrusted () {\n        return this.data && this.data.isTrusted\n    }\n\n    isSameIP (ip) {\n        return this.getIP().then((myIP) => myIP && myIP === ip);\n    }\n\n    _isHostnameIP () {\n        return ip.isV4Format(this.data.hostname) || ip.isV6Format(this.data.hostname)\n    }\n\n    _hasCorrectIP () {\n        return this.data.ip && (ip.isV4Format(this.data.ip) || ip.isV6Format(this.data.ip))\n    }\n\n    _getIPString (ipOrHostname) {\n        return ipOrHostname.includes(':')  ? `[${ipOrHostname}]` : ipOrHostname;\n    }\n\n    _isIPOutdated () {\n        return !this.iplastUpdated || getSecondsPassed(this.iplastUpdated) > this.opts.ipRefreshTimeout\n    }\n}\n\nmodule.exports = {\n    DEFAULT_OPTIONS,\n    DEFAULT_PEER_DATA,\n    PROTOCOLS,\n    Peer\n};\n"
  },
  {
    "path": "src/node/tools/terminal.js",
    "content": "const blessed = require('blessed');\nconst contrib = require('blessed-contrib');\nrequire('colors');\nconst moment = require('moment');\nconst momentDurationFormatSetup = require(\"moment-duration-format\");\n\nmomentDurationFormatSetup(moment);\n\nvar screen = null;\nvar mainBox = null;\nvar statusBox = null;\nvar peersBox = null;\nvar progress = null;\n\nmodule.exports = {\n    init,\n    exit: ensureScreen(exit),\n    log: log,\n    beat: ensureScreen(beat),\n    settings: ensureScreen(settings),\n    ports: ensureScreen(ports),\n    nodes: ensureScreen(nodes)\n};\n\nfunction init (name, version, onExit) {\n    screen = blessed.screen({\n        smartCSR: true\n    });\n    screen.key(['escape', 'q', 'C-c'], function() {\n        exit();\n        return onExit();\n    });\n\n    mainBox = blessed.box({\n        top: '51%',\n        left: 'center',\n        width: '100%',\n        height: '49%',\n        content: 'Nelson Console:',\n        scrollable: true,\n        alwaysScroll: true,\n        tags: true,\n        border: {\n            type: 'line'\n        },\n        style: {\n            fg: 'white',\n            border: {\n                fg: '#f0f0f0'\n            }\n        }\n    });\n\n    statusBox = blessed.box({\n        top: '0%',\n        left: '0%',\n        width: '30%',\n        height: '51%',\n        content: `${name} v.${version} - Status`.green.bold,\n        tags: true,\n        border: {\n            type: 'line'\n        },\n        style: {\n            fg: 'white',\n            border: {\n                fg: '#f0f0f0'\n            }\n        }\n    });\n\n    peersBox = blessed.box({\n        top: '0%',\n        left: '50%',\n        width: '50%',\n        height: '51%',\n        content: 'Peers'.green.bold,\n        tags: true,\n        border: {\n            type: 'line'\n        },\n        style: {\n            fg: 'white',\n            border: {\n                fg: '#f0f0f0'\n            }\n        }\n    });\n\n    progress = contrib.donut({\n        top: '0%',\n        left: '30%',\n        width: '20%',\n        height: '51%',\n        radius: 8,\n        arcWidth: 3,\n        remainColor: 'black',\n        yPadding: 2,\n        border: {\n            type: 'line'\n        },\n        style: {\n            fg: 'white',\n            border: {\n                fg: '#f0f0f0'\n            }\n        }\n    });\n\n    screen.append(mainBox);\n    screen.append(statusBox);\n    screen.append(peersBox);\n    screen.append(progress);\n    mainBox.focus();\n    screen.render();\n}\n\nfunction log () {\n    const msg = Array.from(arguments).join(' ');\n    if (!screen) {\n        console.log(msg);\n        return;\n    }\n    mainBox.pushLine(msg);\n    mainBox.setScrollPerc(100);\n    screen.render();\n}\n\nfunction beat ({ epoch, cycle, startDate, pctEpoch, pctCycle }) {\n    const duration = moment.duration(moment().diff(startDate)).format('d [days] h [hours] m [minutes]');\n    statusBox.setLine(3, `Online: ${duration}`.bold.yellow);\n    statusBox.setLine(4, `Epoch: ${epoch}`.bold);\n    statusBox.setLine(5, `Cycle: ${cycle}`.bold);\n    progress.setData([\n        {percent: pctEpoch, label: 'epoch', color: 'green'},\n        {percent: pctCycle, label: 'cycle', color: 'green'}\n    ]);\n    screen.render();\n}\n\nfunction settings ({ epochInterval, cycleInterval, startDate }) {\n    const startDateString = moment(startDate).format('dddd, MMMM Do YYYY, HH:mm:ss.SSS');\n    statusBox.setLine(2, `Started on: ${startDateString}`.yellow);\n    statusBox.setLine(6, `Epoch Interval: ${epochInterval}s`);\n    statusBox.setLine(7, `Cycle Interval: ${cycleInterval}s`);\n    screen.render();\n}\n\nfunction ports ({ port, apiPort, IRIPort, TCPPort, UDPPort }) {\n    statusBox.setLine(8, `Port: ${port}`.dim.cyan);\n    statusBox.setLine(9, `API Port: ${apiPort}`.dim.cyan);\n    statusBox.setLine(10, `IRI Port: ${IRIPort}`.dim.cyan);\n    statusBox.setLine(11, `TCP Port: ${TCPPort}`.dim.cyan);\n    statusBox.setLine(12, `UDP Port: ${UDPPort}`.dim.cyan);\n    screen.render();\n}\n\nfunction nodes ({ nodes, connected }) {\n    peersBox.setLine(2, `Count: ${nodes.length} (Connected: ${connected.length || 0})`.bold);\n    peersBox.setLine(4, `Connections:`.bold);\n    const lines = peersBox.getLines().length;\n    for (let i = lines -1; i >= 5; i--) {\n        peersBox.clearLine(i)\n    }\n    if (!Array.isArray(connected) || connected.length === 0) {\n        peersBox.setLine(5, 'do not worry, this may take a while...'.dim);\n    }\n    else {\n        connected.forEach((connection, i) => {\n            let id = `${connection.hostname||connection.ip}:${connection.port}`.bold.cyan;\n            id = connection.name ? `${id} (${connection.name})`.bold.cyan : id;\n            // const weight = `[trust: ${(connection.trust * 100).toFixed(6)}]`.green;\n            peersBox.setLine(5 + i, `${id} -> ${connection.IRIProtocol || 'udp'}`);\n        });\n    }\n    screen.render();\n}\n\nfunction ensureScreen (f) {\n    return function () {\n        if (!screen) {\n            return;\n        }\n        return f(...arguments);\n    }\n}\n\nfunction exit () {\n    screen.destroy();\n    screen = null;\n}\n"
  },
  {
    "path": "src/node/tools/utils.js",
    "content": "const ip = require('ip');\nconst dns = require('dns');\nconst version = require('../../../package.json').version;\nconst crypto = require('crypto');\nconst md5 = require('md5');\n\n/**\n * Resolves IP or hostname to IP. If failed, returns the input.\n * @param {string} ipOrHostName\n * @returns {Promise<string>}\n */\nfunction getIP (ipOrHostName) {\n    return new Promise((resolve) => {\n        if (ip.isV4Format(ipOrHostName) || ip.isV6Format(ipOrHostName)) {\n            return resolve(ipOrHostName);\n        }\n        dns.resolve(ipOrHostName, 'A', (error, results) => {\n            resolve(error ? ipOrHostName : results[0]);\n        })\n    });\n}\n\n/**\n * Returns number of seconds that passed starting from a given time.\n * @param time\n * @returns {number}\n */\nfunction getSecondsPassed (time) {\n    if (!time) {\n        return 0;\n    }\n    return ((new Date()).getTime() - time.getTime()) / 1000\n}\n\n/**\n * Creates a random 96-char-long hexadecimal identifier.\n * @returns {string}\n */\nfunction createIdentifier () {\n    return crypto.randomBytes(48).toString('hex')\n}\n\n/**\n * Creates an MD5 hash from the given address\n * @param {string} address\n * @returns {string}\n */\nfunction getPeerIdentifier (address) {\n    return md5(address)\n}\n\n/**\n * Returns a random number\n * @param {number} min\n * @param {number} max\n * @returns {number}\n */\nfunction getRandomInt(min, max) {\n    min = Math.ceil(min);\n    max = Math.floor(max);\n    return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive\n}\n\n/**\n * Shuffles the array\n * @param {Array} array\n * @returns {Array}\n */\nfunction shuffleArray (array) {\n    return array.sort(() => Math.random() - 0.5)\n}\n\n/**\n * Returns Nelson version number\n */\nfunction getVersion () {\n    return version;\n}\n\n/**\n * Returns whether the provided version number is the same major version as the current Nelson.\n * @param {string} otherVersion\n */\nfunction isSameMajorVersion (otherVersion) {\n    return version.split('.')[0] === otherVersion.split('.')[0]\n}\n\n/**\n * Returns whether the provided string is a valid Nelson neighbor representation\n * @param str\n * @returns {boolean}\n */\nfunction validNeighbor(str) {\n    const tokens = str.split('/');\n    return (\n        (tokens.length >= 2 && tokens.length <= 5) &&\n        (Number.isInteger(parseInt(tokens[1]))) &&\n        (!tokens[2] || Number.isInteger(parseInt(tokens[2]))) &&\n        (!tokens[3] || Number.isInteger(parseInt(tokens[3]))) &&\n        (!tokens[4] || !!parseFloat(tokens[4]))\n    );\n}\n\nmodule.exports = {\n    getIP,\n    createIdentifier,\n    getPeerIdentifier,\n    getRandomInt,\n    getSecondsPassed,\n    getVersion,\n    isSameMajorVersion,\n    shuffleArray,\n    validNeighbor\n};\n"
  },
  {
    "path": "src/simulation/__tests__/node-network-integration-test.js",
    "content": "const { spawnMockedNetwork } = require('../network');\nconst { DEFAULT_OPTIONS } = require('../../node/node');\n\ndescribe('Node Network', () => {\n    it('should run correctly', (done) => {\n        const onError = () => { throw new Error('A node exited for some reason...'); };\n        const network = spawnMockedNetwork({ onError, silent: true });\n        process.on('SIGINT', network.end);\n        process.on('SIGTERM', network.end);\n        setTimeout(() => {\n            network.end().then(() => {\n                const stats = Object.values(network.getStats());\n                const connections = stats.filter(s => !s.isMaster).map(s => s.connections.connected);\n                const allConnections = stats.map(s => s.connections.connected);\n                const peers = stats.filter(s => !s.isMaster).map(s => s.peers.length);\n\n                // All normal nodes should be connected to at least one neighbor\n                expect(connections.filter(n => n < 1)).toHaveLength(0);\n                // All nodes should respect the maximal neighbors settings.\n                expect(allConnections.filter(n => n > DEFAULT_OPTIONS.incomingMax + DEFAULT_OPTIONS.outgoingMax + 1))\n                    .toHaveLength(0);\n                // All nodes should have a considerable amount of peers in their DB.\n                expect(peers.filter(n => n < 40)).toHaveLength(0);\n                done();\n            });\n        }, 390000)\n    }, 400000);\n});\n"
  },
  {
    "path": "src/simulation/bin/nelson.js",
    "content": "#!/usr/bin/env node\nconst program = require('commander');\nconst { initMockedNode } = require('../index');\nconst version = require('../../../package.json').version;\n\nconst parseNeighbors = (val) => val.split(' ');\nconst parseProtocol = (val) => val.toLowerCase();\nconst parseNumber = (v) => parseInt(v);\n\nprocess.on('unhandledRejection', (reason, p) => {\n    console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);\n    // application specific logging, throwing an error, or other logic here\n});\n\nprogram\n    .version(version)\n    .option('-n, --neighbors [value]', 'Trusted neighbors', parseNeighbors, [])\n    .option('-c, --cycle [value]', 'Cycle interval in seconds', parseNumber, 10)\n    .option('-e, --epoch [value]', 'Epoch interval in seconds', parseNumber, 60)\n    .option('-p, --port [value]', 'Nelson port', parseNumber, 14265)\n    .option('--IRIProtocol [value]', 'IRI protocol to use: udp or tcp, prefertcp, preferudp or any', parseProtocol ,'any')\n    .option('--master [value]', 'Is master node', false)\n    .option('-s, --silent [value]', 'Silent', false)\n    .parse(process.argv);\n\ninitMockedNode({\n    port: program.port,\n    dataPort: program.dataPort,\n    silent: program.silent,\n    cycleInterval: program.cycle,\n    epochInterval: program.epoch,\n    neighbors: program.neighbors,\n    isMaster: program.master,\n    IRIProtocol: program.IRIProtocol\n});\n"
  },
  {
    "path": "src/simulation/bin/network.js",
    "content": "#!/usr/bin/env node\nconst program = require('commander');\nconst { spawnMockedNetwork, DEFAULT_OPTS } = require('../network');\nconst version = require('../../../package.json').version;\n\nconst parseNumber = (v) => parseInt(v);\n\nprocess.on('unhandledRejection', (reason, p) => {\n    console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);\n    // application specific logging, throwing an error, or other logic here\n});\n\nprogram\n    .version(version)\n    .option('-c, --cycleInterval [value]', 'Cycle interval in seconds', parseNumber, DEFAULT_OPTS.cycleInterval)\n    .option('-e, --epochInterval [value]', 'Epoch interval in seconds', parseNumber, DEFAULT_OPTS.epochInterval)\n    .option('-p, --startingPort [value]', 'Starting port', parseNumber, DEFAULT_OPTS.startingPort)\n    .option('-n, --nodesCount [value]', 'Normal nodes amount', parseNumber, DEFAULT_OPTS.nodesCount)\n    .option('-m, --masterNodesCount [value]', 'Master nodes amount', parseNumber, DEFAULT_OPTS.masterNodesCount)\n    .option('-s, --silent', 'Silent', DEFAULT_OPTS.silent)\n    .parse(process.argv);\n\nconst proc = spawnMockedNetwork({\n    ...program,\n    callback: stats.onCallback\n});\n\nprocess.on('SIGINT', proc.end);\nprocess.on('SIGTERM', proc.end);\n"
  },
  {
    "path": "src/simulation/index.js",
    "content": "const { initMockedNode } = require('./node');\nconst { spawnMockedNetwork } = require('./network');\n\nmodule.exports = {\n    initMockedNode,\n    spawnMockedNetwork\n};\n"
  },
  {
    "path": "src/simulation/network.js",
    "content": "const { utils, peer } = require('../node');\nconst { spawnNode } = require('./node');\n\nconst DEFAULT_OPTS = {\n    silent: false,\n    cycleInterval: 12,\n    epochInterval: 36,\n    nodesCount: 47,\n    masterNodesCount: 3,\n    startingPort: 14265,\n    nodeStartDelayRange: [0, 6000],\n    callbackInterval: 5000,\n    onStats: (nodeStats) => {},\n    onError: (nodeStats) => {}\n};\n\n// TODO: update jsdoc\n/**\n * Initializes and starts a simulation with a set of mocked nodes.\n * First, the master nodes are started all at once. Then the normal nodes are added sequentially\n * in a random interval (using options.nodeStartDelayRange).\n *\n * @param {object} options\n * @param {number} options.nodesCount - the amount of \"normal\" nodes to start\n * @param {number} options.masterNodesCount - the amount of \"master\" nodes to start\n * @param {number} options.startingPort - Port number to start the nodes from incrementally\n * @param {function} options.onStats - periodic callback with connection summary\n * @param {function} options.onError - on child process exit or error\n * @param {boolean} options.callbackInterval - in seconds\n * @param {number[]} options.nodeStartDelayRange - how many ms to wait between normal nodes starting\n * @returns {{stop: (function()), onPeersAdded: (function())}}\n */\nfunction spawnMockedNetwork (options) {\n    const {\n        nodesCount, masterNodesCount, startingPort, silent, cycleInterval, epochInterval,\n        onStats, onError, callbackInterval, nodeStartDelayRange\n    } = { ...DEFAULT_OPTS, ...options};\n    const baseNodeOptions = { silent, cycleInterval, epochInterval };\n    const allNodes = [];\n    const masterNodeURIs = [];\n    const stats = {};\n\n    let ended = false;\n    let cbInterval = null;\n\n    const hasEnded = () => ended;\n    const prc = (p, port) => {\n        p.on('message', (s) => stats[port] = s);\n        p.on('error', onError);\n        p.on('exit', () => !hasEnded() && onError());\n    };\n\n    // Start the master nodes\n    for(let x = 0; x < masterNodesCount; x++) {\n        const port = startingPort + x;\n        const TCPPort = port + 10000;\n        const UDPPort = port + 20000;\n        if (ended) {\n            break\n        }\n        const node = spawnNode({ ...baseNodeOptions, port, isMaster: true, neighbors: masterNodeURIs });\n        prc(node, port);\n        allNodes.push(node);\n        masterNodeURIs.push(`localhost/${port}/${TCPPort}/${UDPPort}`);\n    }\n\n    // Sequentially start the normal nodes\n    const promise = \".\".repeat(nodesCount).split('').reduce((promise, value, y) => {\n        return hasEnded() ? promise : promise.then((nodes) => {\n            return new Promise((resolve) => {\n                if (hasEnded()) {\n                    return resolve(nodes)\n                }\n                setTimeout(() => {\n                    if (hasEnded()) {\n                        return resolve(nodes)\n                    }\n                    const port = startingPort + masterNodesCount + y;\n                    const TCPPort = port + 10000;\n                    const UDPPort = port + 20000;\n                    const node = spawnNode({\n                        ...baseNodeOptions,\n                        port, TCPPort, UDPPort, neighbors: masterNodeURIs,\n                        IRIProtocol: peer.PROTOCOLS[utils.getRandomInt(0, peer.PROTOCOLS.length)]\n                    });\n                    prc(node, port);\n                    resolve([ ...nodes, node ])\n                }, utils.getRandomInt(nodeStartDelayRange[0], nodeStartDelayRange[1]));\n            })\n        });\n    }, Promise.resolve(allNodes));\n\n    const end = () => {\n        ended = true;\n        cbInterval && clearInterval(cbInterval);\n        return promise.then((nodes) => {\n            !silent && console.log('STOPPING NETWORK');\n            nodes.forEach(n => n.kill());\n        })\n    };\n\n    if (callbackInterval) {\n        cbInterval = setInterval(() => onStats(stats), callbackInterval);\n    }\n\n    return {\n        end,\n        onPeersAdded: () => { return promise },\n        getStats: () => stats,\n        getNodeProcesses: () => allNodes\n    }\n\n}\n\nmodule.exports = {\n    DEFAULT_OPTS,\n    spawnMockedNetwork\n};\n"
  },
  {
    "path": "src/simulation/node.js",
    "content": "const cp = require('child_process');\nconst { Node } = require('../node/__mocks__/node');\n\n/**\n * Initializes a mocked node with given options\n * @param {object} options - see Node options for details.\n * @returns {Promise<Node>}\n */\nfunction initMockedNode (options) {\n    const node = new Node(options);\n    return node.start().then((n) => {\n        n.log('initialized!');\n        return node;\n    });\n}\n\n/**\n * Spawns a node process\n * @param {object} options\n */\nfunction spawnNode(options={}, silent) {\n    let opts = [];\n    options.port && opts.push('-p') && opts.push(`${options.port}`);\n    options.isMaster && opts.push(`--master`);\n    options.IRIProtocol && opts.push(`--IRIProtocol`) && opts.push(options.IRIProtocol);\n    options.neighbors && options.neighbors.length && opts.push('-n') && opts.push(`${options.neighbors.join(' ')}`);\n    options.silent && opts.push('-s');\n    options.cycleInterval && opts.push('-c') && opts.push(`${options.cycleInterval}`);\n    options.epochInterval && opts.push('-e') && opts.push(`${options.epochInterval}`);\n    return cp.fork(`${__dirname}/bin/nelson.js`, opts, { silent });\n}\n\nmodule.exports = {\n    spawnNode,\n    initMockedNode\n};\n"
  }
]