├── .coveralls.yml ├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── benchmarks ├── data │ ├── block-367238.json │ ├── block-367239.json │ └── block-367240.json └── index.js ├── bin └── qtumcore-node ├── docs ├── bus.md ├── development.md ├── index.md ├── node.md ├── release.md ├── scaffold.md ├── services.md ├── services │ ├── bitcoind.md │ └── web.md └── upgrade.md ├── index.js ├── lib ├── bus.js ├── cli │ ├── bitcore.js │ ├── bitcored.js │ ├── daemon.js │ └── main.js ├── errors.js ├── index.js ├── logger.js ├── node.js ├── scaffold │ ├── add.js │ ├── call-method.js │ ├── create.js │ ├── default-base-config.js │ ├── default-config.js │ ├── find-config.js │ ├── remove.js │ └── start.js ├── service.js ├── services │ ├── qtumd.js │ └── web.js └── utils.js ├── package.json ├── regtest ├── cluster.js ├── data │ ├── .gitignore │ ├── bitcoin.conf │ ├── bitcoind.crt │ ├── bitcoind_no_pass.key │ ├── node1 │ │ └── bitcoin.conf │ ├── node2 │ │ └── bitcoin.conf │ ├── node3 │ │ └── bitcoin.conf │ └── qtum.conf ├── node.js ├── p2p.js └── qtumd.js ├── scripts ├── download └── regtest ├── test ├── bus.integration.js ├── bus.unit.js ├── data │ ├── badqtum.conf │ ├── bitcoin-transactions.json │ ├── default.qtum.conf │ ├── hashes.json │ ├── livenet-345003.json │ ├── qtum.conf │ ├── testnet-blocks.json │ └── transaction.json ├── index.unit.js ├── logger.unit.js ├── node.unit.js ├── scaffold │ ├── add.integration.js │ ├── call-method.unit.js │ ├── create.integration.js │ ├── default-base-config.integration.js │ ├── default-config.integration.js │ ├── find-config.integration.js │ ├── remove.integration.js │ ├── start.integration.js │ └── start.unit.js ├── services │ ├── qtumd.unit.js │ └── web.unit.js └── utils.unit.js └── yarn.lock /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: DvrDb09a8vhPlVf6DT4cGBjcFOi6DfZN1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | node_modules/* 3 | coverage/* 4 | .lock-wscript 5 | *.swp 6 | *.Makefile 7 | *.target.gyp.mk 8 | *.node 9 | *.sln 10 | *.sdf 11 | *.vcxproj 12 | *.suo 13 | *.opensdf 14 | *.filters 15 | *.user 16 | *.project 17 | **/*.dylib 18 | **/*.so 19 | **/*.old 20 | **/*.files 21 | **/*.config 22 | **/*.creator 23 | *.log 24 | .DS_Store 25 | bin/bitcoin* 26 | bin/SHA256SUMS.asc 27 | regtest/data/node1/regtest 28 | regtest/data/node2/regtest 29 | regtest/data/node3/regtest 30 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "browser": true, 4 | "camelcase": false, 5 | "curly": true, 6 | "devel": false, 7 | "eqeqeq": true, 8 | "esnext": true, 9 | "freeze": true, 10 | "immed": true, 11 | "indent": 2, 12 | "latedef": true, 13 | "newcap": false, 14 | "noarg": true, 15 | "node": true, 16 | "noempty": true, 17 | "nonew": true, 18 | "quotmark": "single", 19 | "regexp": true, 20 | "smarttabs": false, 21 | "strict": true, 22 | "trailing": true, 23 | "undef": true, 24 | "unused": true, 25 | "maxparams": 4, 26 | "maxstatements": 15, 27 | "maxcomplexity": 10, 28 | "maxdepth": 3, 29 | "maxlen": 120, 30 | "multistr": true, 31 | "predef": [ 32 | "after", 33 | "afterEach", 34 | "before", 35 | "beforeEach", 36 | "describe", 37 | "exports", 38 | "it", 39 | "module", 40 | "require" 41 | ] 42 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | env: 4 | - CXX=g++-4.8 CC=gcc-4.8 5 | addons: 6 | apt: 7 | sources: 8 | - ubuntu-toolchain-r-test 9 | packages: 10 | - g++-4.8 11 | - gcc-4.8 12 | - libzmq3-dev 13 | node_js: 14 | - "v0.10.25" 15 | - "v0.12.7" 16 | - "v4" 17 | script: 18 | - npm run regtest 19 | - npm run test 20 | - npm run jshint 21 | after_success: 22 | - npm run coveralls -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015 BitPay, Inc. 2 | 3 | Parts of this software are based on Bitcoin Core 4 | Copyright (c) 2009-2015 The Bitcoin Core developers 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Qtumcore Node 2 | ============ 3 | 4 | A QTUM full node for building applications and services with Node.js. A node is extensible and can be configured to run additional services. 5 | 6 | ## Getting Started 7 | 8 | 1. Install nvm https://github.com/creationix/nvm 9 | 10 | ```bash 11 | nvm i v6 12 | nvm use v6 13 | ``` 14 | 2. Install mongo https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/ 15 | 16 | 3. Install qtum-bitcore https://github.com/qtumproject/qtum-bitcore - with ZMQ ! 17 | 18 | ```bash 19 | # with ZMQ 20 | sudo apt-get install libzmq3-dev 21 | ``` 22 | 4. Install qtumcore-node 23 | 24 | ```bash 25 | npm i https://github.com/qtumproject/qtumcore-node.git#master 26 | 27 | $(npm bin)/qtumcore-node create mynode 28 | 29 | cd mynode 30 | 31 | ``` 32 | 5. Edit qtumcore-node.json 33 | 34 | ```json 35 | { 36 | "network": "livenet", 37 | "port": 3001, 38 | "services": [ 39 | "qtumd", 40 | "web" 41 | ], 42 | "servicesConfig": { 43 | "qtumd": { 44 | "spawn": { 45 | "datadir": "/home/user/.qtum", 46 | "exec": "/home/user/qtum-bitcore/src/qtumd" 47 | } 48 | } 49 | } 50 | } 51 | ``` 52 | 6. Edit qtum.conf 53 | 54 | ``` 55 | server=1 56 | whitelist=127.0.0.1 57 | txindex=1 58 | addressindex=1 59 | timestampindex=1 60 | spentindex=1 61 | zmqpubrawtx=tcp://127.0.0.1:28332 62 | zmqpubhashblock=tcp://127.0.0.1:28332 63 | rpcallowip=127.0.0.1 64 | rpcuser=user 65 | rpcpassword=password 66 | rpcport=18332 67 | reindex=1 68 | gen=0 69 | addrindex=1 70 | logevents=1 71 | ``` 72 | 7. Run Node 73 | 74 | ``` 75 | $(npm bin)/qtumcore-node start 76 | ``` 77 | 78 | ## Add-on Services 79 | 80 | There are several add-on services available to extend the functionality of Qtumcore: 81 | 82 | - [QTUM Insight API](https://github.com/qtumproject/insight-api) 83 | - [QTUM Explorer](https://github.com/qtumproject/qtum-explorer) 84 | 85 | ## Contributing 86 | 87 | 88 | 89 | ## License 90 | -------------------------------------------------------------------------------- /benchmarks/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var benchmark = require('benchmark'); 4 | var bitcoin = require('bitcoin'); 5 | var async = require('async'); 6 | var maxTime = 20; 7 | 8 | console.log('Bitcoin Service native interface vs. Bitcoin JSON RPC interface'); 9 | console.log('----------------------------------------------------------------------'); 10 | 11 | // To run the benchmarks a fully synced Bitcore Core directory is needed. The RPC comands 12 | // can be modified to match the settings in bitcoin.conf. 13 | 14 | var fixtureData = { 15 | blockHashes: [ 16 | '00000000fa7a4acea40e5d0591d64faf48fd862fa3561d111d967fc3a6a94177', 17 | '000000000017e9e0afc4bc55339f60ffffb9cbe883f7348a9fbc198a486d5488', 18 | '000000000019ddb889b534c5d85fca2c91a73feef6fd775cd228dea45353bae1', 19 | '0000000000977ac3d9f5261efc88a3c2d25af92a91350750d00ad67744fa8d03' 20 | ], 21 | txHashes: [ 22 | '5523b432c1bd6c101bee704ad6c560fd09aefc483f8a4998df6741feaa74e6eb', 23 | 'ff48393e7731507c789cfa9cbfae045b10e023ce34ace699a63cdad88c8b43f8', 24 | '5d35c5eebf704877badd0a131b0a86588041997d40dbee8ccff21ca5b7e5e333', 25 | '88842f2cf9d8659c3434f6bc0c515e22d87f33e864e504d2d7117163a572a3aa', 26 | ] 27 | }; 28 | 29 | var qtumd = require('../').services.Bitcoin({ 30 | node: { 31 | datadir: process.env.HOME + '/.bitcoin', 32 | network: { 33 | name: 'testnet' 34 | } 35 | } 36 | }); 37 | 38 | qtumd.on('error', function(err) { 39 | console.error(err.message); 40 | }); 41 | 42 | qtumd.start(function(err) { 43 | if (err) { 44 | throw err; 45 | } 46 | console.log('Bitcoin Core started'); 47 | }); 48 | 49 | qtumd.on('ready', function() { 50 | 51 | console.log('Bitcoin Core ready'); 52 | 53 | var client = new bitcoin.Client({ 54 | host: 'localhost', 55 | port: 18332, 56 | user: 'bitcoin', 57 | pass: 'local321' 58 | }); 59 | 60 | async.series([ 61 | function(next) { 62 | 63 | var c = 0; 64 | var hashesLength = fixtureData.blockHashes.length; 65 | var txLength = fixtureData.txHashes.length; 66 | 67 | function bitcoindGetBlockNative(deffered) { 68 | if (c >= hashesLength) { 69 | c = 0; 70 | } 71 | var hash = fixtureData.blockHashes[c]; 72 | qtumd.getBlock(hash, function(err, block) { 73 | if (err) { 74 | throw err; 75 | } 76 | deffered.resolve(); 77 | }); 78 | c++; 79 | } 80 | 81 | function bitcoindGetBlockJsonRpc(deffered) { 82 | if (c >= hashesLength) { 83 | c = 0; 84 | } 85 | var hash = fixtureData.blockHashes[c]; 86 | client.getBlock(hash, false, function(err, block) { 87 | if (err) { 88 | throw err; 89 | } 90 | deffered.resolve(); 91 | }); 92 | c++; 93 | } 94 | 95 | function bitcoinGetTransactionNative(deffered) { 96 | if (c >= txLength) { 97 | c = 0; 98 | } 99 | var hash = fixtureData.txHashes[c]; 100 | qtumd.getTransaction(hash, true, function(err, tx) { 101 | if (err) { 102 | throw err; 103 | } 104 | deffered.resolve(); 105 | }); 106 | c++; 107 | } 108 | 109 | function bitcoinGetTransactionJsonRpc(deffered) { 110 | if (c >= txLength) { 111 | c = 0; 112 | } 113 | var hash = fixtureData.txHashes[c]; 114 | client.getRawTransaction(hash, function(err, tx) { 115 | if (err) { 116 | throw err; 117 | } 118 | deffered.resolve(); 119 | }); 120 | c++; 121 | } 122 | 123 | var suite = new benchmark.Suite(); 124 | 125 | suite.add('qtumd getblock (native)', bitcoindGetBlockNative, { 126 | defer: true, 127 | maxTime: maxTime 128 | }); 129 | 130 | suite.add('qtumd getblock (json rpc)', bitcoindGetBlockJsonRpc, { 131 | defer: true, 132 | maxTime: maxTime 133 | }); 134 | 135 | suite.add('qtumd gettransaction (native)', bitcoinGetTransactionNative, { 136 | defer: true, 137 | maxTime: maxTime 138 | }); 139 | 140 | suite.add('qtumd gettransaction (json rpc)', bitcoinGetTransactionJsonRpc, { 141 | defer: true, 142 | maxTime: maxTime 143 | }); 144 | 145 | suite 146 | .on('cycle', function(event) { 147 | console.log(String(event.target)); 148 | }) 149 | .on('complete', function() { 150 | console.log('Fastest is ' + this.filter('fastest').pluck('name')); 151 | console.log('----------------------------------------------------------------------'); 152 | next(); 153 | }) 154 | .run(); 155 | } 156 | ], function(err) { 157 | if (err) { 158 | throw err; 159 | } 160 | console.log('Finished'); 161 | qtumd.stop(function(err) { 162 | if (err) { 163 | console.error('Fail to stop services: ' + err); 164 | process.exit(1); 165 | } 166 | process.exit(0); 167 | }); 168 | }); 169 | }); 170 | -------------------------------------------------------------------------------- /bin/qtumcore-node: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var bitcore = require('../lib/cli/bitcore'); 4 | bitcore(); 5 | -------------------------------------------------------------------------------- /docs/bus.md: -------------------------------------------------------------------------------- 1 | # Bus 2 | The bus provides a way to subscribe to events from any of the services running. It's implemented abstract from transport specific implementation. The primary use of the bus in Qtumcore Node is for subscribing to events via a web socket. 3 | 4 | ## Opening/Closing 5 | 6 | ```javascript 7 | 8 | // a node is needed to be able to open a bus 9 | var node = new Node(configuration); 10 | 11 | // will create a new bus that is ready to subscribe to events 12 | var bus = node.openBus(); 13 | 14 | // will remove all event listeners 15 | bus.close(); 16 | ``` 17 | 18 | ## Subscribing/Unsubscribing 19 | 20 | ```javascript 21 | 22 | // subscribe to all transaction events 23 | bus.subscribe('qtumd/rawtransaction'); 24 | 25 | // to subscribe to new block hashes 26 | bus.subscribe('qtumd/hashblock'); 27 | 28 | // unsubscribe 29 | bus.unsubscribe('qtumd/rawtransaction'); 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | # Setting up Development Environment 2 | 3 | ## Install Node.js 4 | 5 | Install Node.js by your favorite method, or use Node Version Manager by following directions at https://github.com/creationix/nvm 6 | 7 | ```bash 8 | nvm install v4 9 | ``` 10 | 11 | ## Fork and Download Repositories 12 | 13 | To develop qtumcore-node: 14 | 15 | ```bash 16 | cd ~ 17 | git clone git@github.com:/qtumcore-node.git 18 | git clone git@github.com:/qtumcore-lib.git 19 | ``` 20 | 21 | To develop qtum or to compile from source: 22 | 23 | ```bash 24 | git clone git@github.com:/qtumcoin.git 25 | git fetch origin : 26 | git checkout 27 | ``` 28 | **Note**: See qtum documentation for building qtum on your platform. 29 | 30 | 31 | ## Install Development Dependencies 32 | 33 | For Ubuntu: 34 | ```bash 35 | sudo apt-get install libzmq3-dev 36 | sudo apt-get install build-essential 37 | ``` 38 | **Note**: Make sure that libzmq-dev is not installed, it should be removed when installing libzmq3-dev. 39 | 40 | 41 | For Mac OS X: 42 | ```bash 43 | brew install zeromq 44 | ``` 45 | 46 | ## Install and Symlink 47 | 48 | ```bash 49 | cd bitcore-lib 50 | npm install 51 | cd ../bitcore-node 52 | npm install 53 | ``` 54 | **Note**: If you get a message about not being able to download qtum distribution, you'll need to compile qtumd from source, and setup your configuration to use that version. 55 | 56 | 57 | We now will setup symlinks in `qtumcore-node` *(repeat this for any other modules you're planning on developing)*: 58 | ```bash 59 | cd node_modules 60 | rm -rf qtumcore-lib 61 | ln -s ~/qtumcore-lib 62 | rm -rf qtumd-rpc 63 | ln -s ~/qtumd-rpc 64 | ``` 65 | 66 | And if you're compiling or developing qtumcoin: 67 | ```bash 68 | cd ../bin 69 | ln -sf ~/qtum/src/qtumd 70 | ``` 71 | 72 | ## Run Tests 73 | 74 | If you do not already have mocha installed: 75 | ```bash 76 | npm install mocha -g 77 | ``` 78 | 79 | To run all test suites: 80 | ```bash 81 | cd qtumcore-node 82 | npm run regtest 83 | npm run test 84 | ``` 85 | 86 | To run a specific unit test in watch mode: 87 | ```bash 88 | mocha -w -R spec test/services/qtumd.unit.js 89 | ``` 90 | 91 | To run a specific regtest: 92 | ```bash 93 | mocha -R spec regtest/qtumd.js 94 | ``` 95 | 96 | ## Running a Development Node 97 | 98 | To test running the node, you can setup a configuration that will specify development versions of all of the services: 99 | 100 | ```bash 101 | cd ~ 102 | mkdir devnode 103 | cd devnode 104 | mkdir node_modules 105 | touch qtumcore-node.json 106 | touch package.json 107 | ``` 108 | 109 | Edit `qtumcore-node.json` with something similar to: 110 | ```json 111 | { 112 | "network": "livenet", 113 | "port": 3001, 114 | "services": [ 115 | "qtumd", 116 | "web", 117 | "insight-api", 118 | "insight-ui", 119 | "" 120 | ], 121 | "servicesConfig": { 122 | "qtumd": { 123 | "spawn": { 124 | "datadir": "/home//.qtum", 125 | "exec": "/home//qtum/src/qtumd" 126 | } 127 | } 128 | } 129 | } 130 | ``` 131 | 132 | **Note**: To install services [qtum-insight-api](https://github.com/qtumproject/insight-api) and [qtum-explorer](https://github.com/qtumproject/qtum-explorer) you'll need to clone the repositories locally. 133 | 134 | Setup symlinks for all of the services and dependencies: 135 | 136 | ```bash 137 | cd node_modules 138 | ln -s ~/qtumcore-lib 139 | ln -s ~/qtumcore-node 140 | ln -s ~/qtum-insight-api 141 | ln -s ~/qtum-explorer 142 | ``` 143 | 144 | Make sure that the `/qtum.conf` has the necessary settings, for example: 145 | ``` 146 | server=1 147 | whitelist=127.0.0.1 148 | txindex=1 149 | addressindex=1 150 | timestampindex=1 151 | spentindex=1 152 | zmqpubrawtx=tcp://127.0.0.1:28332 153 | zmqpubhashblock=tcp://127.0.0.1:28332 154 | rpcallowip=127.0.0.1 155 | rpcuser=user 156 | rpcpassword=password 157 | rpcport=18332 158 | reindex=1 159 | gen=0 160 | addrindex=1 161 | logevents=1 162 | ``` 163 | 164 | From within the `devnode` directory with the configuration file, start the node: 165 | ```bash 166 | ../qtumcore-node/bin/qtumcore-node start 167 | ``` -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /docs/node.md: -------------------------------------------------------------------------------- 1 | # Node 2 | A node represents a collection of services that are loaded together. For more information about services, please see the [Services Documentation](services.md). 3 | 4 | ## API Documentation 5 | - `start()` - Will start the node's services in the correct order based on the dependencies of a service. 6 | - `stop()` - Will stop the node's services. 7 | - `openBus()` - Will create a new event bus to subscribe to events. 8 | - `getAllAPIMethods()` - Returns information about all of the API methods from the services. 9 | - `getAllPublishEvents()` - Returns information about publish events. 10 | - `getServiceOrder()` - Returns an array of service modules. 11 | - `services..` - Additional API methods exposed by each service. The services for the node are defined when the node instance is constructed. 12 | 13 | ## Example Usage 14 | 15 | ```js 16 | 17 | var index = require('qtumcore-node'); 18 | var Qtum = index.services.Qtum; 19 | var Node = index.Node; 20 | 21 | var configuration = { 22 | datadir: '/home/user/.qtum', 23 | network: 'testnet', 24 | services: [ 25 | { 26 | name: 'qtumd', 27 | module: Qtum, 28 | config: {} 29 | } 30 | ] 31 | }; 32 | 33 | var node = new Node(configuration); 34 | 35 | node.start(function() { 36 | //start the node so the node.on('ready') is actually called. 37 | }); 38 | 39 | node.on('ready', function() { 40 | console.log('Qtumcoin Node Ready'); 41 | }); 42 | 43 | node.on('error', function(err) { 44 | console.error(err); 45 | }); 46 | 47 | // shutdown the node 48 | node.stop(function() { 49 | // the shutdown is complete 50 | }); 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/release.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qtumproject/qtumcore-node/cf5678b4c48d2734a6aa32c1961b5af851ab70bf/docs/release.md -------------------------------------------------------------------------------- /docs/scaffold.md: -------------------------------------------------------------------------------- 1 | # Scaffold 2 | A collection of functions for creating, managing, starting, stopping and interacting with a Qtumcore node. 3 | 4 | ## Install 5 | This function will add a service to a node by installing the necessary dependencies and modifying the `qtumcore-node.json` configuration. 6 | 7 | ## Start 8 | This function will load a configuration file `qtumcore-node.json` and instantiate and start a node based on the configuration. 9 | 10 | ## Find Config 11 | This function will recursively find a configuration `qtumcore-node.json` file in parent directories and return the result. 12 | 13 | ## Default Config 14 | This function will return a default configuration with the default services based on environment variables, and will default to using the standard `/home/user/.qtum` data directory. 15 | 16 | ## Uninstall 17 | This function will remove a service from a node by uninstalling the necessary dependencies and modifying the `qtumcore-node.json` configuration. 18 | 19 | ## Call Method 20 | This function will call an API method on a node via the JSON-RPC interface. 21 | -------------------------------------------------------------------------------- /docs/services.md: -------------------------------------------------------------------------------- 1 | # Services 2 | qtumcore Node has a service module system that can start up additional services that can include additional: 3 | - Blockchain indexes (e.g. querying balances for addresses) 4 | - API methods 5 | - HTTP routes 6 | - Event types to publish and subscribe 7 | 8 | The `qtumcore-node.json` file describes which services will load for a node: 9 | 10 | ```json 11 | { 12 | "services": [ 13 | "qtumd", "web" 14 | ] 15 | } 16 | ``` 17 | 18 | Services correspond with a Node.js module as described in 'package.json', for example: 19 | 20 | ```json 21 | { 22 | "dependencies": { 23 | "qtumcore-lib": "^0.0.1", 24 | "qtumcore-node": "^0.0.1", 25 | "qtum-insight-api": "^0.0.1" 26 | } 27 | } 28 | ``` 29 | 30 | _Note:_ If you already have a qtumcore-node database, and you want to query data from previous blocks in the blockchain, you will need to reindex. Reindexing right now means deleting your qtumcore-node database and resyncing. 31 | 32 | ## Using Services Programmatically 33 | If, instead, you would like to run a custom node, you can include services by including them in your configuration object when initializing a new node. 34 | 35 | ```js 36 | //Require qtumcore 37 | var qtumcore = require('qtumcore-node'); 38 | 39 | //Services 40 | var Qtum = qtumcore.services.Qtum; 41 | var Web = qtumcore.services.Web; 42 | 43 | var myNode = new qtumcore.Node({ 44 | network: 'regtest', 45 | services: [ 46 | { 47 | name: 'qtumd', 48 | module: Qtum, 49 | config: { 50 | spawn: { 51 | datadir: '/home//.qtum', 52 | exec: '/home//qtumcore-node/bin/qtumd' 53 | } 54 | } 55 | }, 56 | { 57 | name: 'web', 58 | module: Web, 59 | config: { 60 | port: 3001 61 | } 62 | } 63 | ] 64 | }); 65 | ``` 66 | 67 | Now that you've loaded your services you can access them via `myNode.services..`. For example if you wanted to check the balance of an address, you could access the address service like so. 68 | 69 | ```js 70 | myNode.services.qtumd.getAddressBalance('1HB5XMLmzFVj8ALj6mfBsbifRoD4miY36v', false, function(err, total) { 71 | console.log(total.balance); //Satoshi amount of this address 72 | }); 73 | ``` 74 | 75 | ## Writing a Service 76 | A new service can be created by inheriting from `Node.Service` and implementing these methods and properties: 77 | - `Service.dependencies` - An array of services that are needed, this will determine the order that services are started on the node. 78 | - `Service.prototype.start()` - Called to start up the service. 79 | - `Service.prototype.stop()` - Called to stop the service. 80 | - `Service.prototype.blockHandler()` - Will be called when a block is added or removed from the chain, and is useful for updating a database view/index. 81 | - `Service.prototype.getAPIMethods()` - Describes which API methods that this service includes, these methods can then be called over the JSON-RPC API, as well as the command-line utility. 82 | - `Service.prototype.getPublishEvents()` - Describes which events can be subscribed to for this service, useful to subscribe to events over the included web socket API. 83 | - `Service.prototype.setupRoutes()` - A service can extend HTTP routes on an express application by implementing this method. 84 | 85 | The `package.json` for the service module can either export the `Node.Service` directly, or specify a specific module to load by including `"qtumcoreNode": "lib/qtumcore-node.js"`. 86 | 87 | Please take a look at some of the existing services for implementation specifics. 88 | 89 | -------------------------------------------------------------------------------- /docs/services/bitcoind.md: -------------------------------------------------------------------------------- 1 | # Qtumcoin Service 2 | 3 | ## Configuration 4 | 5 | The default configuration will include a "spawn" configuration in "qtumd". This defines the location of the block chain database and the location of the `qtumd` daemon executable. The below configuration points to a local clone of `qtum`, and will start `qtumd` automatically with your Node.js application. 6 | 7 | ```json 8 | "servicesConfig": { 9 | "qtumd": { 10 | "spawn": { 11 | "datadir": "/home/user/.qtum", 12 | "exec": "/home/user/qtum-core/src/qtumd" 13 | } 14 | } 15 | } 16 | ``` 17 | 18 | It's also possible to connect to separately managed `qtumd` processes with round-robin quering, for example: 19 | 20 | ```json 21 | "servicesConfig": { 22 | "qtumd": { 23 | "connect": [ 24 | { 25 | "rpchost": "127.0.0.1", 26 | "rpcport": 30521, 27 | "rpcuser": "qtumuser1", 28 | "rpcpassword": "qtumrpcpassword1", 29 | "zmqpubrawtx": "tcp://127.0.0.1:30611" 30 | }, 31 | { 32 | "rpchost": "127.0.0.1", 33 | "rpcport": 30522, 34 | "rpcuser": "qtumuser2", 35 | "rpcpassword": "qtumrpcpassword2", 36 | "zmqpubrawtx": "tcp://127.0.0.1:30622" 37 | }, 38 | { 39 | "rpchost": "127.0.0.1", 40 | "rpcport": 30523, 41 | "rpcuser": "qtumuser3", 42 | "rpcpassword": "qtumrpcpassword3", 43 | "zmqpubrawtx": "tcp://127.0.0.1:30633" 44 | } 45 | ] 46 | } 47 | } 48 | ``` 49 | 50 | **Note**: For detailed example configuration see [`regtest/cluster.js`](regtest/cluster.js) 51 | 52 | 53 | ## API Documentation 54 | Methods are available by directly interfacing with the service: 55 | 56 | ```js 57 | node.services.qtumd. 58 | ``` 59 | 60 | ### Chain 61 | 62 | **Getting Latest Blocks** 63 | 64 | ```js 65 | // gives the block hashes sorted from low to high within a range of timestamps 66 | var high = 1460393372; // Mon Apr 11 2016 12:49:25 GMT-0400 (EDT) 67 | var low = 1460306965; // Mon Apr 10 2016 12:49:25 GMT-0400 (EDT) 68 | node.services.qtumd.getBlockHashesByTimestamp(high, low, function(err, blockHashes) { 69 | //... 70 | }); 71 | 72 | // get the current tip of the chain 73 | node.services.qtumd.getBestBlockHash(function(err, blockHash) { 74 | //... 75 | }) 76 | ``` 77 | 78 | **Getting Synchronization and Node Status** 79 | 80 | ```js 81 | // gives a boolean if the daemon is fully synced (not the initial block download) 82 | node.services.qtumd.isSynced(function(err, synced) { 83 | //... 84 | }) 85 | 86 | // gives the current estimate of blockchain download as a percentage 87 | node.services.qtumd.syncPercentage(function(err, percent) { 88 | //... 89 | }); 90 | 91 | // gives information about the chain including total number of blocks 92 | node.services.qtumd.getInfo(function(err, info) { 93 | //... 94 | }); 95 | ``` 96 | 97 | **Generate Blocks** 98 | 99 | ```js 100 | // will generate a block for the "regtest" network (development purposes) 101 | var numberOfBlocks = 10; 102 | node.services.qtumd.generateBlock(numberOfBlocks, function(err, blockHashes) { 103 | //... 104 | }); 105 | ``` 106 | 107 | ### Blocks and Transactions 108 | 109 | **Getting Block Information** 110 | 111 | It's possible to query blocks by both block hash and by height. Blocks are given as Node.js Buffers and can be parsed via qtumcore: 112 | 113 | ```js 114 | var blockHeight = 0; 115 | node.services.qtumd.getRawBlock(blockHeight, function(err, blockBuffer) { 116 | if (err) { 117 | throw err; 118 | } 119 | var block = qtumcore.Block.fromBuffer(blockBuffer); 120 | console.log(block); 121 | }; 122 | 123 | // get a qtumcore object of the block (as above) 124 | node.services.qtumd.getBlock(blockHash, function(err, block) { 125 | //... 126 | }; 127 | 128 | // get only the block header and index (including chain work, height, and previous hash) 129 | node.services.qtumd.getBlockHeader(blockHeight, function(err, blockHeader) { 130 | //... 131 | }); 132 | 133 | // get the block with a list of txids 134 | node.services.qtumd.getBlockOverview(blockHash, function(err, blockOverview) { 135 | //... 136 | }; 137 | ``` 138 | 139 | **Retrieving and Sending Transactions** 140 | 141 | Get a transaction asynchronously by reading it from disk: 142 | 143 | ```js 144 | var txid = '7426c707d0e9705bdd8158e60983e37d0f5d63529086d6672b07d9238d5aa623'; 145 | node.services.qtumd.getRawTransaction(txid, function(err, transactionBuffer) { 146 | if (err) { 147 | throw err; 148 | } 149 | var transaction = qtumcore.Transaction().fromBuffer(transactionBuffer); 150 | }); 151 | 152 | // get a qtumcore object of the transaction (as above) 153 | node.services.qtumd.getTransaction(txid, function(err, transaction) { 154 | //... 155 | }); 156 | 157 | // retrieve the transaction with input values, fees, spent and block info 158 | node.services.qtumd.getDetailedTransaction(txid, function(err, transaction) { 159 | //... 160 | }); 161 | ``` 162 | 163 | Send a transaction to the network: 164 | 165 | ```js 166 | var numberOfBlocks = 3; 167 | node.services.qtumd.estimateFee(numberOfBlocks, function(err, feesPerKilobyte) { 168 | //... 169 | }); 170 | 171 | node.services.qtumd.sendTransaction(transaction.serialize(), function(err, hash) { 172 | //... 173 | }); 174 | ``` 175 | 176 | ### Addresses 177 | 178 | **Get Unspent Outputs** 179 | 180 | One of the most common uses will be to retrieve unspent outputs necessary to create a transaction, here is how to get the unspent outputs for an address: 181 | 182 | ```js 183 | var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'; 184 | node.services.qtumd.getAddressUnspentOutputs(address, options, function(err, unspentOutputs) { 185 | // see below 186 | }); 187 | ``` 188 | 189 | The `unspentOutputs` will have the format: 190 | 191 | ```js 192 | [ 193 | { 194 | address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW', 195 | txid: '9d956c5d324a1c2b12133f3242deff264a9b9f61be701311373998681b8c1769', 196 | outputIndex: 1, 197 | height: 150, 198 | satoshis: 1000000000, 199 | script: '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac', 200 | confirmations: 3 201 | } 202 | ] 203 | ``` 204 | 205 | **View Balances** 206 | 207 | ```js 208 | var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'; 209 | node.services.qtumd.getAddressBalance(address, options, function(err, balance) { 210 | // balance will be in satoshis with "received" and "balance" 211 | }); 212 | ``` 213 | 214 | **View Address History** 215 | 216 | This method will give history of an address limited by a range of block heights by using the "start" and "end" arguments. The "start" value is the more recent, and greater, block height. The "end" value is the older, and lesser, block height. This feature is most useful for synchronization as previous history can be omitted. Furthermore for large ranges of block heights, results can be paginated by using the "from" and "to" arguments. 217 | 218 | If "queryMempool" is set as true (it is true by default), it will show unconfirmed transactions from the qtum mempool. However, if you specify "start" and "end", "queryMempool" is ignored and is always false. 219 | 220 | If "queryMempoolOnly" is set as true (it is false by default), it will show *only* unconfirmed transactions from mempool. 221 | 222 | ```js 223 | var addresses = ['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW']; 224 | var options = { 225 | start: 345000, 226 | end: 344000, 227 | queryMempool: true // since we presented range, queryMempool will be ignored 228 | }; 229 | node.services.qtumd.getAddressHistory(addresses, options, function(err, history) { 230 | // see below 231 | }); 232 | ``` 233 | 234 | The history format will be: 235 | 236 | ```js 237 | { 238 | totalCount: 1, // The total number of items within "start" and "end" 239 | items: [ 240 | { 241 | addresses: { 242 | 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW': { 243 | inputIndexes: [], 244 | outputIndexes: [0] 245 | } 246 | }, 247 | satoshis: 1000000000, 248 | tx: // the same format as getDetailedTransaction 249 | } 250 | ] 251 | } 252 | ``` 253 | 254 | **View Address Summary** 255 | 256 | ```js 257 | var address = 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'; 258 | var options = { 259 | noTxList: false 260 | }; 261 | 262 | node.services.qtumd.getAddressSummary(address, options, function(err, summary) { 263 | // see below 264 | }); 265 | ``` 266 | 267 | The `summary` will have the format (values are in satoshis): 268 | 269 | ```js 270 | { 271 | totalReceived: 1000000000, 272 | totalSpent: 0, 273 | balance: 1000000000, 274 | unconfirmedBalance: 1000000000, 275 | appearances: 1, 276 | unconfirmedAppearances: 0, 277 | txids: [ 278 | '3f7d13efe12e82f873f4d41f7e63bb64708fc4c942eb8c6822fa5bd7606adb00' 279 | ] 280 | } 281 | ``` 282 | **Notes**: 283 | - `totalReceived` does not exclude change *(the amount of satoshis originating from the same address)* 284 | - `unconfirmedBalance` is the delta that the unconfirmed transactions have on the total balance *(can be both positive and negative)* 285 | - `unconfirmedAppearances` is the total number of unconfirmed transactions 286 | - `appearances` is the total confirmed transactions 287 | - `txids` Are sorted in block order with the most recent at the beginning. A maximum of 1000 *(default)* will be returned, the `from` and `to` options can be used to get further values. 288 | 289 | 290 | ## Events 291 | The Qtum Service exposes two events via the Bus, and there are a few events that can be directly registered: 292 | 293 | ```js 294 | node.services.qtumd.on('tip', function(blockHash) { 295 | // a new block tip has been added, if there is a rapid update (with a second) this will not emit every tip update 296 | }); 297 | 298 | node.services.qtumd.on('tx', function(transactionBuffer) { 299 | // a new transaction has entered the mempool 300 | }); 301 | 302 | node.services.qtumd.on('block', function(blockHash) { 303 | // a new block has been added 304 | }); 305 | ``` 306 | 307 | For details on instantiating a bus for a node, see the [Bus Documentation](../bus.md). 308 | - Name: `qtumd/rawtransaction` 309 | - Name: `qtumd/hashblock` 310 | - Name: `qtumd/addresstxid`, Arguments: [address, address...] 311 | 312 | **Examples:** 313 | 314 | ```js 315 | bus.subscribe('qtumd/rawtransaction'); 316 | bus.subscribe('qtumd/hashblock'); 317 | bus.subscribe('qtumd/addresstxid', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']); 318 | 319 | bus.on('qtumd/rawtransaction', function(transactionHex) { 320 | //... 321 | }); 322 | 323 | bus.on('qtumd/hashblock', function(blockhashHex) { 324 | //... 325 | }); 326 | 327 | bus.on('qtumd/addresstxid', function(data) { 328 | // data.address; 329 | // data.txid; 330 | }); 331 | ``` 332 | -------------------------------------------------------------------------------- /docs/services/web.md: -------------------------------------------------------------------------------- 1 | # Web Service 2 | The web service creates an express app which can be used by services for setting up web routes for API's, static content, web applications, etc. This allows users to interact with various qtumcore node services over one http or https port. 3 | 4 | In order for your service to add routes, it must implement the `setupRoutes()` and `getRoutePrefix()` methods. 5 | 6 | ## Example 7 | 8 | ```js 9 | MyService.prototype.setupRoutes = function(app, express) { 10 | // Set up routes 11 | app.get('/hello', function(req, res) { 12 | res.send('world'); 13 | }); 14 | 15 | // Serve static content 16 | app.use('/static', express.static(__dirname + '/static')); 17 | }; 18 | 19 | MyService.prototype.getRoutePrefix = function() { 20 | return 'my-service' 21 | }; 22 | ``` 23 | 24 | ## Configuring Web Service for HTTPS 25 | You can run the web service over https by editing your qtumcore node config, setting https to true and adding httpsOptions: 26 | 27 | ```json 28 | { 29 | "port": 3001, 30 | "https": true, 31 | "httpsOptions": { 32 | "key": "path-to-private-key", 33 | "cert": "path-to-certificate" 34 | }, 35 | "services": [ 36 | "web" 37 | ] 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/upgrade.md: -------------------------------------------------------------------------------- 1 | # Upgrade Notes 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib'); 4 | module.exports.Node = require('./lib/node'); 5 | module.exports.Service = require('./lib/service'); 6 | module.exports.errors = require('./lib/errors'); 7 | 8 | module.exports.services = {}; 9 | module.exports.services.Bitcoin = require('./lib/services/qtumd'); 10 | module.exports.services.Qtum = require('./lib/services/qtumd'); 11 | module.exports.services.Web = require('./lib/services/web'); 12 | 13 | module.exports.scaffold = {}; 14 | module.exports.scaffold.create = require('./lib/scaffold/create'); 15 | module.exports.scaffold.add = require('./lib/scaffold/add'); 16 | module.exports.scaffold.remove = require('./lib/scaffold/remove'); 17 | module.exports.scaffold.start = require('./lib/scaffold/start'); 18 | module.exports.scaffold.callMethod = require('./lib/scaffold/call-method'); 19 | module.exports.scaffold.findConfig = require('./lib/scaffold/find-config'); 20 | module.exports.scaffold.defaultConfig = require('./lib/scaffold/default-config'); 21 | 22 | module.exports.cli = {}; 23 | module.exports.cli.main = require('./lib/cli/main'); 24 | module.exports.cli.daemon = require('./lib/cli/daemon'); 25 | module.exports.cli.bitcore = require('./lib/cli/bitcore'); 26 | module.exports.cli.bitcored = require('./lib/cli/bitcored'); 27 | 28 | module.exports.lib = require('qtumcore-lib'); 29 | -------------------------------------------------------------------------------- /lib/bus.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var events = require('events'); 4 | var util = require('util'); 5 | 6 | /** 7 | * The bus represents a connection to node, decoupled from the transport layer, that can 8 | * listen and subscribe to any events that are exposed by available services. Services 9 | * can expose events that can be subscribed to by implementing a `getPublishEvents` method. 10 | * @param {Object} params 11 | * @param {Node} params.node - A reference to the node 12 | */ 13 | function Bus(params) { 14 | events.EventEmitter.call(this); 15 | this.node = params.node; 16 | this.remoteAddress = params.remoteAddress; 17 | } 18 | 19 | util.inherits(Bus, events.EventEmitter); 20 | 21 | /** 22 | * This function will find the service that exposes the event by name and 23 | * call the associated subscribe method with the arguments excluding the 24 | * first argument of this function. 25 | * @param {String} name - The name of the event 26 | */ 27 | Bus.prototype.subscribe = function(name) { 28 | var events = []; 29 | 30 | for(var i in this.node.services) { 31 | var service = this.node.services[i]; 32 | events = events.concat(service.getPublishEvents()); 33 | } 34 | 35 | for (var j = 0; j < events.length; j++) { 36 | var event = events[j]; 37 | var params = Array.prototype.slice.call(arguments).slice(1); 38 | params.unshift(this); 39 | if (name === event.name) { 40 | event.subscribe.apply(event.scope, params); 41 | } 42 | } 43 | }; 44 | 45 | /** 46 | * The inverse of the subscribe method. 47 | * @param {String} name - The name of the event 48 | */ 49 | Bus.prototype.unsubscribe = function(name) { 50 | var events = []; 51 | 52 | for(var i in this.node.services) { 53 | var service = this.node.services[i]; 54 | events = events.concat(service.getPublishEvents()); 55 | } 56 | 57 | for (var j = 0; j < events.length; j++) { 58 | var event = events[j]; 59 | var params = Array.prototype.slice.call(arguments).slice(1); 60 | params.unshift(this); 61 | if (name === event.name) { 62 | event.unsubscribe.apply(event.scope, params); 63 | } 64 | } 65 | }; 66 | 67 | /** 68 | * This function will unsubscribe all events. 69 | */ 70 | Bus.prototype.close = function() { 71 | var events = []; 72 | 73 | for(var i in this.node.services) { 74 | var service = this.node.services[i]; 75 | events = events.concat(service.getPublishEvents()); 76 | } 77 | 78 | // Unsubscribe from all events 79 | for (var j = 0; j < events.length; j++) { 80 | var event = events[j]; 81 | event.unsubscribe.call(event.scope, this); 82 | } 83 | }; 84 | 85 | module.exports = Bus; 86 | -------------------------------------------------------------------------------- /lib/cli/bitcore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Liftoff = require('liftoff'); 4 | 5 | function main(parentServicesPath, additionalServices) { 6 | 7 | var liftoff = new Liftoff({ 8 | name: 'bitcore', 9 | moduleName: 'qtumcore-node', 10 | configName: 'qtumcore-node', 11 | processTitle: 'bitcore' 12 | }).on('require', function (name) { 13 | console.log('Loading:', name); 14 | }).on('requireFail', function (name, err) { 15 | console.log('Unable to load:', name, err); 16 | }).on('respawn', function (flags, child) { 17 | console.log('Detected node flags:', flags); 18 | console.log('Respawned to PID:', child.pid); 19 | }); 20 | 21 | liftoff.launch({ 22 | cwd: process.cwd() 23 | }, function(env){ 24 | 25 | var node; 26 | if (env.configPath && env.modulePath) { 27 | node = require(env.modulePath); 28 | node.cli.main(); 29 | } else { 30 | node = require('../../'); 31 | node.cli.main(parentServicesPath, additionalServices); 32 | } 33 | 34 | }); 35 | 36 | } 37 | 38 | module.exports = main; 39 | -------------------------------------------------------------------------------- /lib/cli/bitcored.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Liftoff = require('liftoff'); 4 | 5 | function main(parentServicesPath, additionalServices) { 6 | 7 | var liftoff = new Liftoff({ 8 | name: 'bitcored', 9 | moduleName: 'qtumcore-node', 10 | configName: 'qtumcore-node', 11 | processTitle: 'bitcored' 12 | }).on('require', function (name) { 13 | console.log('Loading:', name); 14 | }).on('requireFail', function (name, err) { 15 | console.log('Unable to load:', name, err); 16 | }).on('respawn', function (flags, child) { 17 | console.log('Detected node flags:', flags); 18 | console.log('Respawned to PID:', child.pid); 19 | }); 20 | 21 | liftoff.launch({ 22 | cwd: process.cwd() 23 | }, function(env){ 24 | 25 | var node; 26 | 27 | if (env.configPath && env.modulePath) { 28 | node = require(env.modulePath); 29 | node.cli.daemon(); 30 | } else { 31 | node = require('../../'); 32 | node.cli.daemon(parentServicesPath, additionalServices); 33 | } 34 | 35 | }); 36 | 37 | } 38 | 39 | module.exports = main; 40 | -------------------------------------------------------------------------------- /lib/cli/daemon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var program = require('commander'); 4 | var path = require('path'); 5 | var bitcore = require('..'); 6 | 7 | function main(servicesPath, additionalServices) { 8 | /* jshint maxstatements: 100 */ 9 | 10 | var version = bitcore.version; 11 | var start = bitcore.scaffold.start; 12 | var findConfig = bitcore.scaffold.findConfig; 13 | var defaultConfig = bitcore.scaffold.defaultConfig; 14 | 15 | program 16 | .version(version) 17 | .description('Start the current node') 18 | .option('-c, --config ', 'Specify the directory with Bitcore Node configuration'); 19 | 20 | program.parse(process.argv); 21 | 22 | if (program.config) { 23 | program.config = path.resolve(process.cwd(), program.config); 24 | } 25 | var configInfo = findConfig(program.config || process.cwd()); 26 | if (!configInfo) { 27 | configInfo = defaultConfig({ 28 | additionalServices: additionalServices 29 | }); 30 | } 31 | if (servicesPath) { 32 | configInfo.servicesPath = servicesPath; 33 | } 34 | start(configInfo); 35 | } 36 | 37 | module.exports = main; 38 | -------------------------------------------------------------------------------- /lib/cli/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var program = require('commander'); 4 | var path = require('path'); 5 | var bitcorenode = require('..'); 6 | var utils = require('../utils'); 7 | 8 | function main(servicesPath, additionalServices) { 9 | /* jshint maxstatements: 100 */ 10 | 11 | var version = bitcorenode.version; 12 | var create = bitcorenode.scaffold.create; 13 | var add = bitcorenode.scaffold.add; 14 | var start = bitcorenode.scaffold.start; 15 | var remove = bitcorenode.scaffold.remove; 16 | var callMethod = bitcorenode.scaffold.callMethod; 17 | var findConfig = bitcorenode.scaffold.findConfig; 18 | var defaultConfig = bitcorenode.scaffold.defaultConfig; 19 | 20 | program 21 | .version(version); 22 | 23 | program 24 | .command('create ') 25 | .description('Create a new node') 26 | .option('-d, --datadir ', 'Specify the bitcoin database directory') 27 | .option('-t, --testnet', 'Enable testnet as the network') 28 | .action(function(dirname, cmd){ 29 | if (cmd.datadir) { 30 | cmd.datadir = path.resolve(process.cwd(), cmd.datadir); 31 | } 32 | var opts = { 33 | cwd: process.cwd(), 34 | dirname: dirname, 35 | datadir: cmd.datadir || './data', 36 | isGlobal: false 37 | }; 38 | if (cmd.testnet) { 39 | opts.network = 'testnet'; 40 | } 41 | create(opts, function(err) { 42 | if (err) { 43 | throw err; 44 | } 45 | console.log('Successfully created node in directory: ', dirname); 46 | }); 47 | }); 48 | 49 | program 50 | .command('start') 51 | .description('Start the current node') 52 | .option('-c, --config ', 'Specify the directory with Bitcore Node configuration') 53 | .action(function(cmd){ 54 | if (cmd.config) { 55 | cmd.config = path.resolve(process.cwd(), cmd.config); 56 | } 57 | var configInfo = findConfig(cmd.config || process.cwd()); 58 | if (!configInfo) { 59 | configInfo = defaultConfig({ 60 | additionalServices: additionalServices 61 | }); 62 | } 63 | if (servicesPath) { 64 | configInfo.servicesPath = servicesPath; 65 | } 66 | start(configInfo); 67 | }); 68 | 69 | program 70 | .command('install ') 71 | .description('Install a service for the current node') 72 | .action(function(services){ 73 | var configInfo = findConfig(process.cwd()); 74 | if (!configInfo) { 75 | throw new Error('Could not find configuration, see `qtumcore-node create --help`'); 76 | } 77 | var opts = { 78 | path: configInfo.path, 79 | services: services 80 | }; 81 | add(opts, function(err) { 82 | if (err) { 83 | throw err; 84 | } 85 | console.log('Successfully added services(s):', services.join(', ')); 86 | }); 87 | }).on('--help', function() { 88 | console.log(' Examples:'); 89 | console.log(); 90 | console.log(' $ qtumcore-node add wallet-service'); 91 | console.log(' $ qtumcore-node add insight-api'); 92 | console.log(); 93 | }); 94 | 95 | program 96 | .command('uninstall ') 97 | .description('Uninstall a service for the current node') 98 | .action(function(services){ 99 | var configInfo = findConfig(process.cwd()); 100 | if (!configInfo) { 101 | throw new Error('Could not find configuration, see `qtumcore-node create --help`'); 102 | } 103 | var opts = { 104 | path: configInfo.path, 105 | services: services 106 | }; 107 | remove(opts, function(err) { 108 | if (err) { 109 | throw err; 110 | } 111 | console.log('Successfully removed services(s):', services.join(', ')); 112 | }); 113 | }).on('--help', function() { 114 | console.log(' Examples:'); 115 | console.log(); 116 | console.log(' $ qtumcore-node remove wallet-service'); 117 | console.log(' $ qtumcore-node remove insight-api'); 118 | console.log(); 119 | }); 120 | 121 | program 122 | .command('call [params...]') 123 | .description('Call an API method') 124 | .action(function(method, paramsArg) { 125 | var params = utils.parseParamsWithJSON(paramsArg); 126 | var configInfo = findConfig(process.cwd()); 127 | if (!configInfo) { 128 | configInfo = defaultConfig(); 129 | } 130 | var options = { 131 | protocol: 'http', 132 | host: 'localhost', 133 | port: configInfo.config.port 134 | }; 135 | callMethod(options, method, params, function(err, data) { 136 | if (err) { 137 | throw err; 138 | } 139 | console.log(JSON.stringify(data, null, 2)); 140 | }); 141 | }); 142 | 143 | program.parse(process.argv); 144 | 145 | if (process.argv.length === 2) { 146 | program.help(); 147 | } 148 | 149 | } 150 | 151 | module.exports = main; 152 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var createError = require('errno').create; 4 | 5 | var BitcoreNodeError = createError('BitcoreNodeError'); 6 | 7 | var RPCError = createError('RPCError', BitcoreNodeError); 8 | 9 | module.exports = { 10 | Error: BitcoreNodeError, 11 | RPCError: RPCError 12 | }; 13 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var Logger = require('./logger'); 2 | module.exports.errors = require('./errors'); 3 | module.exports.log = new Logger(); 4 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bitcore = require('qtumcore-lib'); 4 | var _ = bitcore.deps._; 5 | var colors = require('colors/safe'); 6 | 7 | /** 8 | * Wraps console.log with some special magic 9 | * @constructor 10 | */ 11 | function Logger(options) { 12 | if (!options) { 13 | options = {}; 14 | } 15 | this.formatting = _.isUndefined(options.formatting) ? Logger.DEFAULT_FORMATTING : options.formatting; 16 | } 17 | 18 | Logger.DEFAULT_FORMATTING = true; 19 | 20 | /** 21 | * Prints an info message 22 | * #info 23 | */ 24 | Logger.prototype.info = function() { 25 | this._log.apply(this, ['blue', 'info'].concat(Array.prototype.slice.call(arguments))); 26 | }; 27 | 28 | /** 29 | * Prints an error message 30 | * #error 31 | */ 32 | Logger.prototype.error = function() { 33 | this._log.apply(this, ['red', 'error'].concat(Array.prototype.slice.call(arguments))); 34 | }; 35 | 36 | /** 37 | * Prints an debug message 38 | * #debug 39 | */ 40 | Logger.prototype.debug = function() { 41 | this._log.apply(this, ['magenta', 'debug'].concat(Array.prototype.slice.call(arguments))); 42 | }; 43 | 44 | /** 45 | * Prints an warn message 46 | * #warn 47 | */ 48 | Logger.prototype.warn = function() { 49 | this._log.apply(this, ['yellow', 'warn'].concat(Array.prototype.slice.call(arguments))); 50 | }; 51 | 52 | /** 53 | * Proxies console.log with color and arg parsing magic 54 | * #_log 55 | */ 56 | Logger.prototype._log = function(color) { 57 | var args = Array.prototype.slice.call(arguments); 58 | args = args.slice(1); 59 | var level = args.shift(); 60 | 61 | if (this.formatting) { 62 | var date = new Date(); 63 | var typeString = colors[color].italic(level + ':'); 64 | args[0] = '[' + date.toISOString() + ']' + ' ' + typeString + ' ' + args[0]; 65 | } 66 | var fn = console[level] || console.log; 67 | fn.apply(console, args); 68 | }; 69 | 70 | module.exports = Logger; 71 | -------------------------------------------------------------------------------- /lib/node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var EventEmitter = require('events').EventEmitter; 5 | var async = require('async'); 6 | var bitcore = require('qtumcore-lib'); 7 | var Networks = bitcore.Networks; 8 | var $ = bitcore.util.preconditions; 9 | var _ = bitcore.deps._; 10 | var index = require('./'); 11 | var log = index.log; 12 | var Bus = require('./bus'); 13 | var errors = require('./errors'); 14 | 15 | /** 16 | * A node is a hub of services, and will manage starting and stopping the services in 17 | * the correct order based the the dependency chain. The node also holds common configuration 18 | * properties that can be shared across services, such as network settings. 19 | * 20 | * The array of services should have the format: 21 | * ```js 22 | * { 23 | * name: 'qtumd', 24 | * config: {}, // options to pass into constructor 25 | * module: ServiceConstructor 26 | * } 27 | * ``` 28 | * 29 | * @param {Object} config - The configuration of the node 30 | * @param {Array} config.formatLogs - Option to disable formatting of logs 31 | * @param {Array} config.services - The array of services 32 | * @param {Number} config.port - The HTTP port for services 33 | * @param {Boolean} config.https - Enable https 34 | * @param {Object} config.httpsOptions - Options for https 35 | * @param {String} config.httpsOptions.key - Path to key file 36 | * @param {String} config.httpsOptions.cert - Path to cert file 37 | * @param {} 38 | */ 39 | function Node(config) { 40 | /* jshint maxstatements: 20 */ 41 | if(!(this instanceof Node)) { 42 | return new Node(config); 43 | } 44 | this.configPath = config.path; 45 | this.errors = errors; 46 | this.log = log; 47 | 48 | if (!_.isUndefined(config.formatLogs)) { 49 | this.log.formatting = config.formatLogs ? true : false; 50 | } 51 | 52 | this.network = null; 53 | this.services = {}; 54 | this._unloadedServices = []; 55 | 56 | // TODO type check the arguments of config.services 57 | if (config.services) { 58 | $.checkArgument(Array.isArray(config.services)); 59 | this._unloadedServices = config.services; 60 | } 61 | this.port = config.port; 62 | this.https = config.https; 63 | this.httpsOptions = config.httpsOptions; 64 | this._setNetwork(config); 65 | } 66 | 67 | util.inherits(Node, EventEmitter); 68 | 69 | /** 70 | * Will set the this.network based on a network string. 71 | * @param {Object} config 72 | * @param {String} config.network - Possible options "testnet", "regtest" or "livenet" 73 | */ 74 | Node.prototype._setNetwork = function(config) { 75 | if (config.network === 'testnet') { 76 | this.network = Networks.get('testnet'); 77 | } else if (config.network === 'regtest') { 78 | Networks.enableRegtest(); 79 | this.network = Networks.get('regtest'); 80 | } else { 81 | this.network = Networks.defaultNetwork; 82 | } 83 | $.checkState(this.network, 'Unrecognized network'); 84 | }; 85 | 86 | /** 87 | * Will instantiate a new Bus for this node. 88 | * @returns {Bus} 89 | */ 90 | Node.prototype.openBus = function(options) { 91 | if (!options) { 92 | options = {}; 93 | } 94 | return new Bus({node: this, remoteAddress: options.remoteAddress}); 95 | }; 96 | 97 | /** 98 | * Will get an array of API method descriptions from all of the available services. 99 | * @returns {Array} 100 | */ 101 | Node.prototype.getAllAPIMethods = function() { 102 | var methods = []; 103 | for(var i in this.services) { 104 | var mod = this.services[i]; 105 | if (mod.getAPIMethods) { 106 | methods = methods.concat(mod.getAPIMethods()); 107 | } 108 | } 109 | return methods; 110 | }; 111 | 112 | /** 113 | * Will get an array of events from all of the available services. 114 | * @returns {Array} 115 | */ 116 | Node.prototype.getAllPublishEvents = function() { 117 | var events = []; 118 | for (var i in this.services) { 119 | var mod = this.services[i]; 120 | if (mod.getPublishEvents) { 121 | events = events.concat(mod.getPublishEvents()); 122 | } 123 | } 124 | return events; 125 | }; 126 | 127 | /** 128 | * Will organize services into the order that they should be started 129 | * based on the service's dependencies. 130 | * @returns {Array} 131 | */ 132 | Node.prototype.getServiceOrder = function() { 133 | 134 | var services = this._unloadedServices; 135 | 136 | // organize data for sorting 137 | var names = []; 138 | var servicesByName = {}; 139 | for (var i = 0; i < services.length; i++) { 140 | var service = services[i]; 141 | names.push(service.name); 142 | servicesByName[service.name] = service; 143 | } 144 | 145 | var stackNames = {}; 146 | var stack = []; 147 | 148 | function addToStack(names) { 149 | for(var i = 0; i < names.length; i++) { 150 | 151 | var name = names[i]; 152 | var service = servicesByName[name]; 153 | $.checkState(service, 'Required dependency "' + name + '" not available.'); 154 | 155 | // first add the dependencies 156 | addToStack(service.module.dependencies); 157 | 158 | // add to the stack if it hasn't been added 159 | if(!stackNames[name]) { 160 | stack.push(service); 161 | stackNames[name] = true; 162 | } 163 | 164 | } 165 | } 166 | 167 | addToStack(names); 168 | 169 | return stack; 170 | }; 171 | 172 | /** 173 | * Will instantiate an instance of the service module, add it to the node 174 | * services, start the service and add available API methods to the node and 175 | * checking for any conflicts. 176 | * @param {Object} serviceInfo 177 | * @param {String} serviceInfo.name - The name of the service 178 | * @param {Object} serviceInfo.module - The service module constructor 179 | * @param {Object} serviceInfo.config - Options to pass into the constructor 180 | * @param {Function} callback - Called when the service is started 181 | * @private 182 | */ 183 | Node.prototype._startService = function(serviceInfo, callback) { 184 | var self = this; 185 | 186 | log.info('Starting ' + serviceInfo.name); 187 | 188 | var config; 189 | if (serviceInfo.config) { 190 | $.checkState(_.isObject(serviceInfo.config)); 191 | $.checkState(!serviceInfo.config.node); 192 | $.checkState(!serviceInfo.config.name); 193 | config = serviceInfo.config; 194 | } else { 195 | config = {}; 196 | } 197 | 198 | config.node = this; 199 | config.name = serviceInfo.name; 200 | var service = new serviceInfo.module(config); 201 | 202 | // include in loaded services 203 | self.services[serviceInfo.name] = service; 204 | 205 | service.start(function(err) { 206 | if (err) { 207 | return callback(err); 208 | } 209 | 210 | // add API methods 211 | if (service.getAPIMethods) { 212 | var methodData = service.getAPIMethods(); 213 | var methodNameConflicts = []; 214 | methodData.forEach(function(data) { 215 | var name = data[0]; 216 | var instance = data[1]; 217 | var method = data[2]; 218 | 219 | if (self[name]) { 220 | methodNameConflicts.push(name); 221 | } else { 222 | self[name] = function() { 223 | return method.apply(instance, arguments); 224 | }; 225 | } 226 | }); 227 | 228 | if (methodNameConflicts.length > 0) { 229 | return callback(new Error('Existing API method(s) exists: ' + methodNameConflicts.join(', '))); 230 | } 231 | } 232 | 233 | callback(); 234 | 235 | }); 236 | 237 | }; 238 | 239 | Node.prototype._logTitle = function() { 240 | if (this.configPath) { 241 | log.info('Using config:', this.configPath); 242 | log.info('Using network:', this.getNetworkName()); 243 | } 244 | }; 245 | 246 | 247 | /** 248 | * Will start all running services in the order based on the dependency chain. 249 | * @param {Function} callback - Called when all services are started 250 | */ 251 | Node.prototype.start = function(callback) { 252 | var self = this; 253 | var servicesOrder = this.getServiceOrder(); 254 | 255 | self._logTitle(); 256 | 257 | async.eachSeries( 258 | servicesOrder, 259 | function(service, next) { 260 | self._startService(service, next); 261 | }, 262 | function(err) { 263 | if (err) { 264 | return callback(err); 265 | } 266 | self.emit('ready'); 267 | callback(); 268 | } 269 | ); 270 | }; 271 | 272 | Node.prototype.getNetworkName = function() { 273 | var network = this.network.name; 274 | if (this.network.regtestEnabled) { 275 | network = 'regtest'; 276 | } 277 | return network; 278 | }; 279 | 280 | /** 281 | * Will stop all running services in the reverse order that they 282 | * were initially started. 283 | * @param {Function} callback - Called when all services are stopped 284 | */ 285 | Node.prototype.stop = function(callback) { 286 | log.info('Beginning shutdown'); 287 | var self = this; 288 | var services = this.getServiceOrder().reverse(); 289 | 290 | this.stopping = true; 291 | this.emit('stopping'); 292 | 293 | async.eachSeries( 294 | services, 295 | function(service, next) { 296 | if (self.services[service.name]) { 297 | log.info('Stopping ' + service.name); 298 | self.services[service.name].stop(next); 299 | } else { 300 | log.info('Stopping ' + service.name + ' (not started)'); 301 | setImmediate(next); 302 | } 303 | }, 304 | callback 305 | ); 306 | }; 307 | 308 | module.exports = Node; 309 | -------------------------------------------------------------------------------- /lib/scaffold/add.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var async = require('async'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var spawn = require('child_process').spawn; 7 | var bitcore = require('qtumcore-lib'); 8 | var utils = require('../utils'); 9 | var $ = bitcore.util.preconditions; 10 | var _ = bitcore.deps._; 11 | 12 | /** 13 | * @param {String} configFilePath - The absolute path to the configuration file 14 | * @param {String} service - The name of the service 15 | * @param {Function} done 16 | */ 17 | function addConfig(configFilePath, service, done) { 18 | $.checkState(utils.isAbsolutePath(configFilePath), 'An absolute path is expected'); 19 | fs.readFile(configFilePath, function(err, data) { 20 | if (err) { 21 | return done(err); 22 | } 23 | var config = JSON.parse(data); 24 | $.checkState( 25 | Array.isArray(config.services), 26 | 'Configuration file is expected to have a services array.' 27 | ); 28 | config.services.push(service); 29 | config.services = _.unique(config.services); 30 | config.services.sort(function(a, b) { 31 | return a > b; 32 | }); 33 | fs.writeFile(configFilePath, JSON.stringify(config, null, 2), done); 34 | }); 35 | } 36 | 37 | /** 38 | * @param {String} configDir - The absolute configuration directory path 39 | * @param {String} service - The name of the service 40 | * @param {Function} done 41 | */ 42 | function addService(configDir, service, done) { 43 | $.checkState(utils.isAbsolutePath(configDir), 'An absolute path is expected'); 44 | var npm = spawn('npm', ['install', service, '--save'], {cwd: configDir}); 45 | 46 | npm.stdout.on('data', function(data) { 47 | process.stdout.write(data); 48 | }); 49 | 50 | npm.stderr.on('data', function(data) { 51 | process.stderr.write(data); 52 | }); 53 | 54 | npm.on('close', function(code) { 55 | if (code !== 0) { 56 | return done(new Error('There was an error installing service: ' + service)); 57 | } else { 58 | return done(); 59 | } 60 | }); 61 | } 62 | 63 | /** 64 | * @param {String} options.cwd - The current working directory 65 | * @param {String} options.dirname - The qtumcore-node configuration directory 66 | * @param {Array} options.services - An array of strings of service names 67 | * @param {Function} done - A callback function called when finished 68 | */ 69 | function add(options, done) { 70 | $.checkArgument(_.isObject(options)); 71 | $.checkArgument(_.isFunction(done)); 72 | $.checkArgument( 73 | _.isString(options.path) && utils.isAbsolutePath(options.path), 74 | 'An absolute path is expected' 75 | ); 76 | $.checkArgument(Array.isArray(options.services)); 77 | 78 | var configPath = options.path; 79 | var services = options.services; 80 | 81 | var bitcoreConfigPath = path.resolve(configPath, 'qtumcore-node.json'); 82 | var packagePath = path.resolve(configPath, 'package.json'); 83 | 84 | if (!fs.existsSync(bitcoreConfigPath) || !fs.existsSync(packagePath)) { 85 | return done( 86 | new Error('Directory does not have a qtumcore-node.json and/or package.json file.') 87 | ); 88 | } 89 | 90 | var oldPackage = JSON.parse(fs.readFileSync(packagePath)); 91 | 92 | async.eachSeries( 93 | services, 94 | function(service, next) { 95 | // npm install --save 96 | addService(configPath, service, function(err) { 97 | if (err) { 98 | return next(err); 99 | } 100 | 101 | // get the name of the service from package.json 102 | var updatedPackage = JSON.parse(fs.readFileSync(packagePath)); 103 | var newDependencies = _.difference( 104 | Object.keys(updatedPackage.dependencies), 105 | Object.keys(oldPackage.dependencies) 106 | ); 107 | $.checkState(newDependencies.length === 1); 108 | oldPackage = updatedPackage; 109 | var serviceName = newDependencies[0]; 110 | 111 | // add service to qtumcore-node.json 112 | addConfig(bitcoreConfigPath, serviceName, next); 113 | }); 114 | }, done 115 | ); 116 | } 117 | 118 | module.exports = add; 119 | -------------------------------------------------------------------------------- /lib/scaffold/call-method.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var socketClient = require('socket.io-client'); 4 | 5 | /** 6 | * Calls a remote node with a method and params 7 | * @param {Object} options 8 | * @param {String} method - The name of the method to call 9 | * @param {Array} params - An array of the params for the method 10 | * @param {Function} done - The callback function 11 | */ 12 | function callMethod(options, method, params, done) { 13 | 14 | var host = options.host; 15 | var protocol = options.protocol; 16 | var port = options.port; 17 | var url = protocol + '://' + host + ':' + port; 18 | var socketOptions = { 19 | reconnection: false, 20 | connect_timeout: 5000 21 | }; 22 | var socket = socketClient(url, socketOptions); 23 | 24 | socket.on('connect', function(){ 25 | socket.send({ 26 | method: method, 27 | params: params, 28 | }, function(response) { 29 | if (response.error) { 30 | return done(new Error(response.error.message)); 31 | } 32 | socket.close(); 33 | done(null, response.result); 34 | }); 35 | }); 36 | 37 | socket.on('connect_error', done); 38 | 39 | return socket; 40 | 41 | } 42 | 43 | module.exports = callMethod; 44 | -------------------------------------------------------------------------------- /lib/scaffold/create.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var spawn = require('child_process').spawn; 4 | var bitcore = require('qtumcore-lib'); 5 | var async = require('async'); 6 | var $ = bitcore.util.preconditions; 7 | var _ = bitcore.deps._; 8 | var path = require('path'); 9 | var packageFile = require('../../package.json'); 10 | var mkdirp = require('mkdirp'); 11 | var fs = require('fs'); 12 | var defaultBaseConfig = require('./default-base-config'); 13 | 14 | var version = '^' + packageFile.version; 15 | 16 | var BASE_PACKAGE = { 17 | description: 'QTUM', 18 | repository: 'https://github.com/user/project', 19 | license: 'MIT', 20 | readme: 'README.md', 21 | dependencies: { 22 | // 'bitcore-lib': '^' + bitcore.version, 23 | // 'bitcore-node': version 24 | 'qtumcore-lib': 'https://github.com/qtumproject/qtumcore-lib.git#master', 25 | 'qtumcore-node': 'https://github.com/qtumproject/qtumcore-node.git#master' 26 | } 27 | }; 28 | 29 | /** 30 | * Will create a directory and bitcoin.conf file for Bitcoin. 31 | * @param {String} dataDir - The absolute path 32 | * @param {Function} done - The callback function called when finished 33 | */ 34 | function createBitcoinDirectory(datadir, done) { 35 | mkdirp(datadir, function(err) { 36 | if (err) { 37 | throw err; 38 | } 39 | 40 | done(); 41 | 42 | // Don't create the configuration yet 43 | }); 44 | } 45 | 46 | /** 47 | * Will create a base Bitcore Node configuration directory and files. 48 | * @param {Object} options 49 | * @param {String} options.network - "testnet" or "livenet" 50 | * @param {String} options.datadir - The bitcoin database directory 51 | * @param {String} configDir - The absolute path 52 | * @param {Boolean} isGlobal - If the configuration depends on globally installed node services. 53 | * @param {Function} done - The callback function called when finished 54 | */ 55 | function createConfigDirectory(options, configDir, isGlobal, done) { 56 | mkdirp(configDir, function(err) { 57 | if (err) { 58 | throw err; 59 | } 60 | var configInfo = defaultBaseConfig(options); 61 | var config = configInfo.config; 62 | 63 | var configJSON = JSON.stringify(config, null, 2); 64 | var packageJSON = JSON.stringify(BASE_PACKAGE, null, 2); 65 | try { 66 | fs.writeFileSync(configDir + '/qtumcore-node.json', configJSON); 67 | if (!isGlobal) { 68 | fs.writeFileSync(configDir + '/package.json', packageJSON); 69 | } 70 | } catch(e) { 71 | done(e); 72 | } 73 | done(); 74 | 75 | }); 76 | } 77 | 78 | /** 79 | * Will setup a directory with a Bitcore Node directory, configuration file, 80 | * bitcoin configuration, and will install all necessary dependencies. 81 | * 82 | * @param {Object} options 83 | * @param {String} options.cwd - The current working directory 84 | * @param {String} options.dirname - The name of the bitcore node configuration directory 85 | * @param {String} options.datadir - The path to the bitcoin datadir 86 | * @param {Function} done - A callback function called when finished 87 | */ 88 | function create(options, done) { 89 | /* jshint maxstatements:20 */ 90 | 91 | $.checkArgument(_.isObject(options)); 92 | $.checkArgument(_.isFunction(done)); 93 | $.checkArgument(_.isString(options.cwd)); 94 | $.checkArgument(_.isString(options.dirname)); 95 | $.checkArgument(_.isBoolean(options.isGlobal)); 96 | $.checkArgument(_.isString(options.datadir)); 97 | 98 | var cwd = options.cwd; 99 | var dirname = options.dirname; 100 | var datadir = options.datadir; 101 | var isGlobal = options.isGlobal; 102 | 103 | var absConfigDir = path.resolve(cwd, dirname); 104 | var absDataDir = path.resolve(absConfigDir, datadir); 105 | 106 | async.series([ 107 | function(next) { 108 | // Setup the the qtumcore-node directory and configuration 109 | if (!fs.existsSync(absConfigDir)) { 110 | var createOptions = { 111 | network: options.network, 112 | datadir: datadir 113 | }; 114 | createConfigDirectory(createOptions, absConfigDir, isGlobal, next); 115 | } else { 116 | next(new Error('Directory "' + absConfigDir+ '" already exists.')); 117 | } 118 | }, 119 | function(next) { 120 | // Setup the bitcoin directory and configuration 121 | if (!fs.existsSync(absDataDir)) { 122 | createBitcoinDirectory(absDataDir, next); 123 | } else { 124 | next(); 125 | } 126 | }, 127 | function(next) { 128 | // Install all of the necessary dependencies 129 | if (!isGlobal) { 130 | var npm = spawn('npm', ['install'], {cwd: absConfigDir}); 131 | 132 | npm.stdout.on('data', function (data) { 133 | process.stdout.write(data); 134 | }); 135 | 136 | npm.stderr.on('data', function (data) { 137 | process.stderr.write(data); 138 | }); 139 | 140 | npm.on('close', function (code) { 141 | if (code !== 0) { 142 | return next(new Error('There was an error installing dependencies.')); 143 | } else { 144 | return next(); 145 | } 146 | }); 147 | 148 | } else { 149 | next(); 150 | } 151 | } 152 | ], done); 153 | 154 | } 155 | 156 | module.exports = create; 157 | -------------------------------------------------------------------------------- /lib/scaffold/default-base-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | /** 6 | * Will return the path and default qtumcore-node configuration on environment variables 7 | * or default locations. 8 | * @param {Object} options 9 | * @param {String} options.network - "testnet" or "livenet" 10 | * @param {String} options.datadir - Absolute path to bitcoin database directory 11 | */ 12 | function getDefaultBaseConfig(options) { 13 | if (!options) { 14 | options = {}; 15 | } 16 | return { 17 | path: process.cwd(), 18 | config: { 19 | network: options.network || 'livenet', 20 | port: 3001, 21 | services: ['qtumd', 'web'], 22 | servicesConfig: { 23 | qtumd: { 24 | spawn: { 25 | datadir: options.datadir || path.resolve(process.env.HOME, '.qtum'), 26 | exec: path.resolve(__dirname, '../../bin/qtumd') 27 | } 28 | } 29 | } 30 | } 31 | }; 32 | } 33 | 34 | module.exports = getDefaultBaseConfig; 35 | -------------------------------------------------------------------------------- /lib/scaffold/default-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var fs = require('fs'); 6 | 7 | /** 8 | * Will return the path and default qtumcore-node configuration. It will search for the 9 | * configuration file in the "~/.bitcore" directory, and if it doesn't exist, it will create one 10 | * based on default settings. 11 | * @param {Object} [options] 12 | * @param {Array} [options.additionalServices] - An optional array of services. 13 | */ 14 | function getDefaultConfig(options) { 15 | /* jshint maxstatements: 40 */ 16 | if (!options) { 17 | options = {}; 18 | } 19 | 20 | var defaultPath = path.resolve(process.env.HOME, './.bitcore'); 21 | var defaultConfigFile = path.resolve(defaultPath, './qtumcore-node.json'); 22 | 23 | if (!fs.existsSync(defaultPath)) { 24 | mkdirp.sync(defaultPath); 25 | } 26 | 27 | var defaultServices = ['qtumd', 'web']; 28 | if (options.additionalServices) { 29 | defaultServices = defaultServices.concat(options.additionalServices); 30 | } 31 | 32 | if (!fs.existsSync(defaultConfigFile)) { 33 | var defaultConfig = { 34 | network: 'livenet', 35 | port: 3001, 36 | services: defaultServices, 37 | servicesConfig: { 38 | qtumd: { 39 | spawn: { 40 | datadir: path.resolve(defaultPath, './data'), 41 | exec: path.resolve(__dirname, '../../bin/qtumd') 42 | } 43 | } 44 | } 45 | }; 46 | fs.writeFileSync(defaultConfigFile, JSON.stringify(defaultConfig, null, 2)); 47 | } 48 | 49 | var defaultDataDir = path.resolve(defaultPath, './data'); 50 | 51 | if (!fs.existsSync(defaultDataDir)) { 52 | mkdirp.sync(defaultDataDir); 53 | } 54 | 55 | var config = JSON.parse(fs.readFileSync(defaultConfigFile, 'utf-8')); 56 | 57 | return { 58 | path: defaultPath, 59 | config: config 60 | }; 61 | 62 | } 63 | 64 | module.exports = getDefaultConfig; 65 | -------------------------------------------------------------------------------- /lib/scaffold/find-config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bitcore = require('qtumcore-lib'); 4 | var $ = bitcore.util.preconditions; 5 | var _ = bitcore.deps._; 6 | var path = require('path'); 7 | var fs = require('fs'); 8 | var utils = require('../utils'); 9 | 10 | /** 11 | * Will return the path and qtumcore-node configuration 12 | * @param {String} cwd - The absolute path to the current working directory 13 | */ 14 | function findConfig(cwd) { 15 | $.checkArgument(_.isString(cwd), 'Argument should be a string'); 16 | $.checkArgument(utils.isAbsolutePath(cwd), 'Argument should be an absolute path'); 17 | var directory = String(cwd); 18 | while (!fs.existsSync(path.resolve(directory, 'qtumcore-node.json'))) { 19 | directory = path.resolve(directory, '../'); 20 | if (directory === '/') { 21 | return false; 22 | } 23 | } 24 | return { 25 | path: directory, 26 | config: require(path.resolve(directory, 'qtumcore-node.json')) 27 | }; 28 | } 29 | 30 | module.exports = findConfig; 31 | -------------------------------------------------------------------------------- /lib/scaffold/remove.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var async = require('async'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var spawn = require('child_process').spawn; 7 | var bitcore = require('qtumcore-lib'); 8 | var $ = bitcore.util.preconditions; 9 | var _ = bitcore.deps._; 10 | var utils = require('../utils'); 11 | 12 | /** 13 | * Will remove a service from qtumcore-node.json 14 | * @param {String} configFilePath - The absolute path to the configuration file 15 | * @param {String} service - The name of the module 16 | * @param {Function} done 17 | */ 18 | function removeConfig(configFilePath, service, done) { 19 | $.checkArgument(utils.isAbsolutePath(configFilePath), 'An absolute path is expected'); 20 | fs.readFile(configFilePath, function(err, data) { 21 | if (err) { 22 | return done(err); 23 | } 24 | var config = JSON.parse(data); 25 | $.checkState( 26 | Array.isArray(config.services), 27 | 'Configuration file is expected to have a services array.' 28 | ); 29 | // remove the service from the configuration 30 | for (var i = 0; i < config.services.length; i++) { 31 | if (config.services[i] === service) { 32 | config.services.splice(i, 1); 33 | } 34 | } 35 | config.services = _.unique(config.services); 36 | config.services.sort(function(a, b) { 37 | return a > b; 38 | }); 39 | fs.writeFile(configFilePath, JSON.stringify(config, null, 2), done); 40 | }); 41 | } 42 | 43 | /** 44 | * Will uninstall a Node.js service and remove from package.json. 45 | * @param {String} configDir - The absolute configuration directory path 46 | * @param {String} service - The name of the service 47 | * @param {Function} done 48 | */ 49 | function uninstallService(configDir, service, done) { 50 | $.checkArgument(utils.isAbsolutePath(configDir), 'An absolute path is expected'); 51 | $.checkArgument(_.isString(service), 'A string is expected for the service argument'); 52 | 53 | var child = spawn('npm', ['uninstall', service, '--save'], {cwd: configDir}); 54 | 55 | child.stdout.on('data', function(data) { 56 | process.stdout.write(data); 57 | }); 58 | 59 | child.stderr.on('data', function(data) { 60 | process.stderr.write(data); 61 | }); 62 | 63 | child.on('close', function(code) { 64 | if (code !== 0) { 65 | return done(new Error('There was an error uninstalling service(s): ' + service)); 66 | } else { 67 | return done(); 68 | } 69 | }); 70 | } 71 | 72 | /** 73 | * Will remove a Node.js service if it is installed. 74 | * @param {String} configDir - The absolute configuration directory path 75 | * @param {String} service - The name of the service 76 | * @param {Function} done 77 | */ 78 | function removeService(configDir, service, done) { 79 | $.checkArgument(utils.isAbsolutePath(configDir), 'An absolute path is expected'); 80 | $.checkArgument(_.isString(service), 'A string is expected for the service argument'); 81 | uninstallService(configDir, service, done); 82 | } 83 | 84 | /** 85 | * Will remove the Node.js service and from the qtumcore-node configuration. 86 | * @param {String} options.cwd - The current working directory 87 | * @param {String} options.dirname - The qtumcore-node configuration directory 88 | * @param {Array} options.services - An array of strings of service names 89 | * @param {Function} done - A callback function called when finished 90 | */ 91 | function remove(options, done) { 92 | $.checkArgument(_.isObject(options)); 93 | $.checkArgument(_.isFunction(done)); 94 | $.checkArgument( 95 | _.isString(options.path) && utils.isAbsolutePath(options.path), 96 | 'An absolute path is expected' 97 | ); 98 | $.checkArgument(Array.isArray(options.services)); 99 | 100 | var configPath = options.path; 101 | var services = options.services; 102 | 103 | var bitcoreConfigPath = path.resolve(configPath, 'qtumcore-node.json'); 104 | var packagePath = path.resolve(configPath, 'package.json'); 105 | 106 | if (!fs.existsSync(bitcoreConfigPath) || !fs.existsSync(packagePath)) { 107 | return done( 108 | new Error('Directory does not have a qtumcore-node.json and/or package.json file.') 109 | ); 110 | } 111 | 112 | async.eachSeries( 113 | services, 114 | function(service, next) { 115 | // if the service is installed remove it 116 | removeService(configPath, service, function(err) { 117 | if (err) { 118 | return next(err); 119 | } 120 | // remove service to qtumcore-node.json 121 | removeConfig(bitcoreConfigPath, service, next); 122 | }); 123 | }, done 124 | ); 125 | } 126 | 127 | module.exports = remove; 128 | -------------------------------------------------------------------------------- /lib/scaffold/start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var BitcoreNode = require('../node'); 5 | var index = require('../'); 6 | var bitcore = require('qtumcore-lib'); 7 | var _ = bitcore.deps._; 8 | var log = index.log; 9 | var shuttingDown = false; 10 | 11 | log.debug = function() {}; 12 | 13 | /** 14 | * Checks for configuration options from version 2. This includes an "address" and 15 | * "db" service, or having "datadir" at the root of the config. 16 | */ 17 | function checkConfigVersion2(fullConfig) { 18 | var datadirUndefined = _.isUndefined(fullConfig.datadir); 19 | var addressDefined = (fullConfig.services.indexOf('address') >= 0); 20 | var dbDefined = (fullConfig.services.indexOf('db') >= 0); 21 | 22 | if (!datadirUndefined || addressDefined || dbDefined) { 23 | 24 | console.warn('\nConfiguration file is not compatible with this version. \n' + 25 | 'A reindex for qtumd is necessary for this upgrade with the "reindex=1" bitcoin.conf option. \n' + 26 | 'There are changes necessary in both bitcoin.conf and qtumcore-node.json. \n\n' + 27 | 'To upgrade please see the details below and documentation at: \n' + 28 | 'https://github.com/bitpay/bitcore-node/blob/bitcoind/docs/upgrade.md \n'); 29 | 30 | if (!datadirUndefined) { 31 | console.warn('Please remove "datadir" and add it to the config at ' + fullConfig.path + ' with:'); 32 | var missingConfig = { 33 | servicesConfig: { 34 | qtumd: { 35 | spawn: { 36 | datadir: fullConfig.datadir, 37 | exec: path.resolve(__dirname, '../../bin/qtumd') 38 | } 39 | } 40 | } 41 | }; 42 | console.warn(JSON.stringify(missingConfig, null, 2) + '\n'); 43 | } 44 | 45 | if (addressDefined || dbDefined) { 46 | console.warn('Please remove "address" and/or "db" from "services" in: ' + fullConfig.path + '\n'); 47 | } 48 | 49 | return true; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | /** 56 | * This function will instantiate and start a Node, requiring the necessary service 57 | * modules, and registering event handlers. 58 | * @param {Object} options 59 | * @param {Object} options.servicesPath - The path to the location of service modules 60 | * @param {String} options.path - The absolute path of the configuration file 61 | * @param {Object} options.config - The parsed qtumcore-node.json configuration file 62 | * @param {Array} options.config.services - An array of services names. 63 | * @param {Object} options.config.servicesConfig - Parameters to pass to each service 64 | * @param {String} options.config.network - 'livenet', 'testnet' or 'regtest 65 | * @param {Number} options.config.port - The port to use for the web service 66 | */ 67 | function start(options) { 68 | /* jshint maxstatements: 20 */ 69 | 70 | var fullConfig = _.clone(options.config); 71 | 72 | var servicesPath; 73 | if (options.servicesPath) { 74 | servicesPath = options.servicesPath; // services are in a different directory than the config 75 | } else { 76 | servicesPath = options.path; // defaults to the same directory 77 | } 78 | 79 | fullConfig.path = path.resolve(options.path, './qtumcore-node.json'); 80 | 81 | if (checkConfigVersion2(fullConfig)) { 82 | process.exit(1); 83 | } 84 | 85 | fullConfig.services = start.setupServices(require, servicesPath, options.config); 86 | 87 | var node = new BitcoreNode(fullConfig); 88 | 89 | // setup handlers for uncaught exceptions and ctrl+c 90 | start.registerExitHandlers(process, node); 91 | 92 | node.on('ready', function() { 93 | log.info('Qtumcore Node ready'); 94 | }); 95 | 96 | node.on('error', function(err) { 97 | log.error(err); 98 | }); 99 | 100 | node.start(function(err) { 101 | if(err) { 102 | log.error('Failed to start services'); 103 | if (err.stack) { 104 | log.error(err.stack); 105 | } 106 | start.cleanShutdown(process, node); 107 | } 108 | }); 109 | 110 | return node; 111 | 112 | } 113 | 114 | /** 115 | * Checks a service for the expected methods 116 | * @param {Object} service 117 | */ 118 | function checkService(service) { 119 | // check that the service supports expected methods 120 | if (!service.module.prototype || 121 | !service.module.dependencies || 122 | !service.module.prototype.start || 123 | !service.module.prototype.stop) { 124 | throw new Error( 125 | 'Could not load service "' + service.name + '" as it does not support necessary methods and properties.' 126 | ); 127 | } 128 | } 129 | 130 | /** 131 | * Will require a module from local services directory first 132 | * and then from available node_modules 133 | * @param {Function} req 134 | * @param {Object} service 135 | */ 136 | function loadModule(req, service) { 137 | try { 138 | // first try in the built-in qtumcore-node services directory 139 | service.module = req(path.resolve(__dirname, '../services/' + service.name)); 140 | } catch(e) { 141 | 142 | // check if the package.json specifies a specific file to use 143 | var servicePackage = req(service.name + '/package.json'); 144 | var serviceModule = service.name; 145 | if (servicePackage.qtumcoreNode) { 146 | serviceModule = service.name + '/' + servicePackage.qtumcoreNode; 147 | } 148 | service.module = req(serviceModule); 149 | } 150 | } 151 | 152 | /** 153 | * This function will loop over the configuration for services and require the 154 | * specified modules, and assemble an array in this format: 155 | * [ 156 | * { 157 | * name: 'qtumd', 158 | * config: {}, 159 | * module: BitcoinService 160 | * } 161 | * ] 162 | * @param {Function} req - The require function to use 163 | * @param {Array} servicesPath - The local path (for requiring services) 164 | * @param {Object} config 165 | * @param {Array} config.services - An array of strings of service names. 166 | * @returns {Array} 167 | */ 168 | function setupServices(req, servicesPath, config) { 169 | 170 | module.paths.push(path.resolve(servicesPath, './node_modules')); 171 | 172 | var services = []; 173 | if (config.services) { 174 | for (var i = 0; i < config.services.length; i++) { 175 | var service = {}; 176 | service.name = config.services[i]; 177 | 178 | var hasConfig = config.servicesConfig && config.servicesConfig[service.name]; 179 | service.config = hasConfig ? config.servicesConfig[service.name] : {}; 180 | 181 | loadModule(req, service); 182 | checkService(service); 183 | 184 | services.push(service); 185 | } 186 | } 187 | return services; 188 | } 189 | 190 | /** 191 | * Will shutdown a node and then the process 192 | * @param {Object} _process - The Node.js process object 193 | * @param {Node} node - The Bitcore Node instance 194 | */ 195 | function cleanShutdown(_process, node) { 196 | node.stop(function(err) { 197 | if(err) { 198 | log.error('Failed to stop services: ' + err); 199 | return _process.exit(1); 200 | } 201 | log.info('Halted'); 202 | _process.exit(0); 203 | }); 204 | } 205 | 206 | /** 207 | * Will handle all the shutdown tasks that need to take place to ensure a safe exit 208 | * @param {Object} options 209 | * @param {String} options.sigint - The signal given was a SIGINT 210 | * @param {Array} options.exit - The signal given was an uncaughtException 211 | * @param {Object} _process - The Node.js process 212 | * @param {Node} node 213 | * @param {Error} error 214 | */ 215 | function exitHandler(options, _process, node, err) { 216 | if (err) { 217 | log.error('uncaught exception:', err); 218 | if(err.stack) { 219 | log.error(err.stack); 220 | } 221 | node.stop(function(err) { 222 | if(err) { 223 | log.error('Failed to stop services: ' + err); 224 | } 225 | _process.exit(-1); 226 | }); 227 | } 228 | if (options.sigint) { 229 | if (!shuttingDown) { 230 | shuttingDown = true; 231 | start.cleanShutdown(_process, node); 232 | } 233 | } 234 | } 235 | 236 | /** 237 | * Will register event handlers to stop the node for `process` events 238 | * `uncaughtException` and `SIGINT`. 239 | * @param {Object} _process - The Node.js process 240 | * @param {Node} node 241 | */ 242 | function registerExitHandlers(_process, node) { 243 | //catches uncaught exceptions 244 | _process.on('uncaughtException', exitHandler.bind(null, {exit:true}, _process, node)); 245 | 246 | //catches ctrl+c event 247 | _process.on('SIGINT', exitHandler.bind(null, {sigint:true}, _process, node)); 248 | } 249 | 250 | module.exports = start; 251 | module.exports.registerExitHandlers = registerExitHandlers; 252 | module.exports.exitHandler = exitHandler; 253 | module.exports.setupServices = setupServices; 254 | module.exports.cleanShutdown = cleanShutdown; 255 | module.exports.checkConfigVersion2 = checkConfigVersion2; 256 | -------------------------------------------------------------------------------- /lib/service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var EventEmitter = require('events').EventEmitter; 5 | 6 | var Service = function(options) { 7 | EventEmitter.call(this); 8 | 9 | this.node = options.node; 10 | this.name = options.name; 11 | }; 12 | 13 | util.inherits(Service, EventEmitter); 14 | 15 | /** 16 | * Describes the dependencies that should be loaded before this service. 17 | */ 18 | Service.dependencies = []; 19 | 20 | /** 21 | * blockHandler 22 | * @param {Block} block - the block being added or removed from the chain 23 | * @param {Boolean} add - whether the block is being added or removed 24 | * @param {Function} callback - call with the leveldb database operations to perform 25 | */ 26 | Service.prototype.blockHandler = function(block, add, callback) { 27 | // implement in the child class 28 | setImmediate(function() { 29 | callback(null, []); 30 | }); 31 | }; 32 | 33 | /** 34 | * the bus events available for subscription 35 | * @return {Array} an array of event info 36 | */ 37 | Service.prototype.getPublishEvents = function() { 38 | // Example: 39 | // return [ 40 | // ['eventname', this, this.subscribeEvent, this.unsubscribeEvent], 41 | // ]; 42 | return []; 43 | }; 44 | 45 | /** 46 | * the API methods to expose 47 | * @return {Array} return array of methods 48 | */ 49 | Service.prototype.getAPIMethods = function() { 50 | // Example: 51 | // return [ 52 | // ['getData', this, this.getData, 1] 53 | // ]; 54 | 55 | return []; 56 | }; 57 | 58 | // Example: 59 | // Service.prototype.getData = function(arg1, callback) { 60 | // 61 | // }; 62 | 63 | /** 64 | * Function which is called when module is first initialized 65 | */ 66 | Service.prototype.start = function(done) { 67 | setImmediate(done); 68 | }; 69 | 70 | /** 71 | * Function to be called when qtumcore-node is stopped 72 | */ 73 | Service.prototype.stop = function(done) { 74 | setImmediate(done); 75 | }; 76 | 77 | /** 78 | * Setup express routes 79 | * @param {Express} app 80 | */ 81 | Service.prototype.setupRoutes = function() { 82 | // Setup express routes here 83 | }; 84 | 85 | Service.prototype.getRoutePrefix = function() { 86 | return this.name; 87 | }; 88 | 89 | module.exports = Service; 90 | -------------------------------------------------------------------------------- /lib/services/web.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var http = require('http'); 5 | var https = require('https'); 6 | var express = require('express'); 7 | var bodyParser = require('body-parser'); 8 | var socketio = require('socket.io'); 9 | var inherits = require('util').inherits; 10 | 11 | var BaseService = require('../service'); 12 | var bitcore = require('qtumcore-lib'); 13 | var _ = bitcore.deps._; 14 | var index = require('../'); 15 | var log = index.log; 16 | 17 | 18 | /** 19 | * This service represents a hub for combining several services over a single HTTP port. Services 20 | * can extend routes by implementing the methods `getRoutePrefix` and `setupRoutes`. Additionally 21 | * events that are exposed via the `getPublishEvents` and API methods exposed via `getAPIMethods` 22 | * will be available over a socket.io connection. 23 | * 24 | * @param {Object} options 25 | * @param {Node} options.node - A reference to the node 26 | * @param {Boolean} options.https - Enable https, will default to node.https settings. 27 | * @param {Object} options.httpsOptions - Options passed into https.createServer, defaults to node settings. 28 | * @param {String} options.httpsOptions.key - Path to key file 29 | * @param {String} options.httpsOptions.cert - Path to cert file 30 | * @param {Boolean} options.enableSocketRPC - Option to enable/disable websocket RPC handling 31 | * @param {Number} options.port - The port for the service, defaults to node settings. 32 | */ 33 | var WebService = function(options) { 34 | var self = this; 35 | this.node = options.node; 36 | this.https = options.https || this.node.https; 37 | this.httpsOptions = options.httpsOptions || this.node.httpsOptions; 38 | this.port = options.port || this.node.port || 3456; 39 | this.host = options.host; 40 | 41 | // set the maximum size of json payload, defaults to express default 42 | // see: https://github.com/expressjs/body-parser#limit 43 | this.jsonRequestLimit = options.jsonRequestLimit || '100kb'; 44 | 45 | this.enableSocketRPC = _.isUndefined(options.enableSocketRPC) ? 46 | WebService.DEFAULT_SOCKET_RPC : options.enableSocketRPC; 47 | 48 | this.node.on('ready', function() { 49 | self.eventNames = self.getEventNames(); 50 | self.setupAllRoutes(); 51 | self.server.listen(self.port, self.host); 52 | self.createMethodsMap(); 53 | }); 54 | }; 55 | 56 | inherits(WebService, BaseService); 57 | 58 | WebService.dependencies = []; 59 | WebService.DEFAULT_SOCKET_RPC = true; 60 | 61 | /** 62 | * Called by Node to start the service 63 | * @param {Function} callback 64 | */ 65 | WebService.prototype.start = function(callback) { 66 | this.app = express(); 67 | this.app.use(bodyParser.json({limit: this.jsonRequestLimit})); 68 | 69 | if(this.https) { 70 | this.transformHttpsOptions(); 71 | this.server = https.createServer(this.httpsOptions, this.app); 72 | } else { 73 | this.server = http.createServer(this.app); 74 | } 75 | 76 | this.io = socketio.listen(this.server); 77 | this.io.on('connection', this.socketHandler.bind(this)); 78 | 79 | setImmediate(callback); 80 | }; 81 | 82 | /** 83 | * Called by Node. stop the service 84 | * @param {Function} callback 85 | */ 86 | WebService.prototype.stop = function(callback) { 87 | var self = this; 88 | 89 | setImmediate(function() { 90 | if(self.server) { 91 | self.server.close(); 92 | } 93 | callback(); 94 | }); 95 | }; 96 | 97 | /** 98 | * This function will iterate over all of the available services gathering 99 | * all of the exposed HTTP routes. 100 | */ 101 | WebService.prototype.setupAllRoutes = function() { 102 | for(var key in this.node.services) { 103 | var subApp = new express(); 104 | var service = this.node.services[key]; 105 | 106 | if(service.getRoutePrefix && service.setupRoutes) { 107 | this.app.use('/' + this.node.services[key].getRoutePrefix(), subApp); 108 | this.node.services[key].setupRoutes(subApp, express); 109 | } else { 110 | log.debug('No routes defined for: ' + key); 111 | } 112 | } 113 | }; 114 | 115 | /** 116 | * This function will construct an API methods map of all of the 117 | * available methods that can be called from enable services. 118 | */ 119 | WebService.prototype.createMethodsMap = function() { 120 | var self = this; 121 | var methods = this.node.getAllAPIMethods(); 122 | this.methodsMap = {}; 123 | 124 | methods.forEach(function(data) { 125 | var name = data[0]; 126 | var instance = data[1]; 127 | var method = data[2]; 128 | var args = data[3]; 129 | self.methodsMap[name] = { 130 | fn: function() { 131 | return method.apply(instance, arguments); 132 | }, 133 | args: args 134 | }; 135 | }); 136 | }; 137 | 138 | /** 139 | * This function will gather all of the available events exposed from 140 | * the enabled services. 141 | */ 142 | WebService.prototype.getEventNames = function() { 143 | var events = this.node.getAllPublishEvents(); 144 | var eventNames = []; 145 | 146 | function addEventName(name) { 147 | if(eventNames.indexOf(name) > -1) { 148 | throw new Error('Duplicate event ' + name); 149 | } 150 | eventNames.push(name); 151 | } 152 | 153 | events.forEach(function(event) { 154 | addEventName(event.name); 155 | 156 | if(event.extraEvents) { 157 | event.extraEvents.forEach(function(name) { 158 | addEventName(name); 159 | }); 160 | } 161 | }); 162 | 163 | return eventNames; 164 | }; 165 | 166 | WebService.prototype._getRemoteAddress = function(socket) { 167 | return socket.client.request.headers['cf-connecting-ip'] || socket.conn.remoteAddress; 168 | }; 169 | 170 | /** 171 | * This function is responsible for managing a socket.io connection, including 172 | * instantiating a new Bus, subscribing/unsubscribing and handling RPC commands. 173 | * @param {Socket} socket - A socket.io socket instance 174 | */ 175 | WebService.prototype.socketHandler = function(socket) { 176 | var self = this; 177 | var remoteAddress = self._getRemoteAddress(socket); 178 | var bus = this.node.openBus({remoteAddress: remoteAddress}); 179 | 180 | if (this.enableSocketRPC) { 181 | socket.on('message', this.socketMessageHandler.bind(this)); 182 | } 183 | 184 | socket.on('subscribe', function(name, params) { 185 | log.info(remoteAddress, 'web socket subscribe:', name); 186 | bus.subscribe(name, params); 187 | }); 188 | 189 | socket.on('unsubscribe', function(name, params) { 190 | log.info(remoteAddress, 'web socket unsubscribe:', name); 191 | bus.unsubscribe(name, params); 192 | }); 193 | 194 | this.eventNames.forEach(function(eventName) { 195 | bus.on(eventName, function() { 196 | if(socket.connected) { 197 | var results = []; 198 | 199 | for(var i = 0; i < arguments.length; i++) { 200 | results.push(arguments[i]); 201 | } 202 | 203 | var params = [eventName].concat(results); 204 | socket.emit.apply(socket, params); 205 | } 206 | }); 207 | }); 208 | 209 | socket.on('disconnect', function() { 210 | log.info(remoteAddress, 'web socket disconnect'); 211 | bus.close(); 212 | }); 213 | }; 214 | 215 | /** 216 | * This method will handle incoming RPC messages to a socket.io connection, 217 | * call the appropriate method, and respond with the result. 218 | * @param {Object} message - The socket.io "message" object 219 | * @param {Function} socketCallback 220 | */ 221 | WebService.prototype.socketMessageHandler = function(message, socketCallback) { 222 | if (this.methodsMap[message.method]) { 223 | var params = message.params; 224 | 225 | if(!params || !params.length) { 226 | params = []; 227 | } 228 | 229 | if(params.length !== this.methodsMap[message.method].args) { 230 | return socketCallback({ 231 | error: { 232 | message: 'Expected ' + this.methodsMap[message.method].args + ' parameter(s)' 233 | } 234 | }); 235 | } 236 | 237 | var callback = function(err, result) { 238 | var response = {}; 239 | if(err) { 240 | response.error = { 241 | message: err.toString() 242 | }; 243 | } 244 | 245 | if(result) { 246 | response.result = result; 247 | } 248 | 249 | socketCallback(response); 250 | }; 251 | 252 | params = params.concat(callback); 253 | this.methodsMap[message.method].fn.apply(this, params); 254 | } else { 255 | socketCallback({ 256 | error: { 257 | message: 'Method Not Found' 258 | } 259 | }); 260 | } 261 | }; 262 | 263 | /** 264 | * This method will read `key` and `cert` from disk based on `httpsOptions` and 265 | * replace the options with the files. 266 | */ 267 | WebService.prototype.transformHttpsOptions = function() { 268 | if(!this.httpsOptions || !this.httpsOptions.key || !this.httpsOptions.cert) { 269 | throw new Error('Missing https options'); 270 | } 271 | 272 | this.httpsOptions = { 273 | key: fs.readFileSync(this.httpsOptions.key), 274 | cert: fs.readFileSync(this.httpsOptions.cert) 275 | }; 276 | }; 277 | 278 | module.exports = WebService; 279 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var MAX_SAFE_INTEGER = 0x1fffffffffffff; // 2 ^ 53 - 1 4 | 5 | var utils = {}; 6 | utils.isHash = function isHash(value) { 7 | return typeof value === 'string' && value.length === 64 && /^[0-9a-fA-F]+$/.test(value); 8 | }; 9 | 10 | utils.isSafeNatural = function isSafeNatural(value) { 11 | return typeof value === 'number' && 12 | isFinite(value) && 13 | Math.floor(value) === value && 14 | value >= 0 && 15 | value <= MAX_SAFE_INTEGER; 16 | }; 17 | 18 | utils.startAtZero = function startAtZero(obj, key) { 19 | if (!obj.hasOwnProperty(key)) { 20 | obj[key] = 0; 21 | } 22 | }; 23 | 24 | utils.isAbsolutePath = require('path').isAbsolute; 25 | if (!utils.isAbsolutePath) { 26 | utils.isAbsolutePath = require('path-is-absolute'); 27 | } 28 | 29 | utils.parseParamsWithJSON = function parseParamsWithJSON(paramsArg) { 30 | var params = paramsArg.map(function(paramArg) { 31 | var param; 32 | try { 33 | param = JSON.parse(paramArg); 34 | } catch(err) { 35 | param = paramArg; 36 | } 37 | return param; 38 | }); 39 | return params; 40 | }; 41 | 42 | module.exports = utils; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qtumcore-node", 3 | "description": "Full node with extended capabilities using Qtumcore and Qtumcoin Core", 4 | "author": "QTUM ", 5 | "version": "0.0.7", 6 | "main": "./index.js", 7 | "repository": "https://github.com/qtumproject/qtumcore-node.git", 8 | "homepage": "https://github.com/qtumproject/qtumcore-node", 9 | "bugs": { 10 | "url": "https://github.com/qtumproject/qtumcore-node/issues" 11 | }, 12 | "contributors": [], 13 | "bin": { 14 | "qtumcore-node": "./bin/qtumcore-node" 15 | }, 16 | "scripts": { 17 | "test": "mocha -R spec --recursive", 18 | "regtest": "./scripts/regtest", 19 | "jshint": "jshint --reporter=node_modules/jshint-stylish ./lib", 20 | "coverage": "istanbul cover _mocha -- --recursive", 21 | "coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- --recursive -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 22 | }, 23 | "tags": [ 24 | "qtum" 25 | ], 26 | "dependencies": { 27 | "async": "^1.3.0", 28 | "bitcore-lib": "^0.13.13", 29 | "body-parser": "^1.13.3", 30 | "colors": "^1.1.2", 31 | "commander": "^2.8.1", 32 | "errno": "^0.1.4", 33 | "express": "^4.13.3", 34 | "liftoff": "^2.2.0", 35 | "lru-cache": "^4.0.1", 36 | "mkdirp": "0.5.0", 37 | "path-is-absolute": "^1.0.0", 38 | "qtumcore-lib": "https://github.com/qtumproject/qtumcore-lib.git#master", 39 | "qtumd-rpc": "https://github.com/qtumproject/qtumd-rpc.git#master", 40 | "semver": "^5.0.1", 41 | "socket.io": "^1.4.5", 42 | "socket.io-client": "^1.4.5", 43 | "zmq": "^2.14.0" 44 | }, 45 | "optionalDependencies": { 46 | "bufferutil": "~1.2.1", 47 | "utf-8-validate": "~1.2.1" 48 | }, 49 | "devDependencies": { 50 | "benchmark": "1.0.0", 51 | "bitcore-p2p": "^1.1.0", 52 | "chai": "^3.5.0", 53 | "coveralls": "^2.11.9", 54 | "istanbul": "^0.4.3", 55 | "jshint": "^2.9.2", 56 | "jshint-stylish": "^2.1.0", 57 | "mocha": "^2.4.5", 58 | "proxyquire": "^1.3.1", 59 | "rimraf": "^2.4.2", 60 | "sinon": "^1.15.4" 61 | }, 62 | "license": "MIT" 63 | } 64 | -------------------------------------------------------------------------------- /regtest/cluster.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var async = require('async'); 5 | var spawn = require('child_process').spawn; 6 | 7 | var BitcoinRPC = require('qtumd-rpc'); 8 | var rimraf = require('rimraf'); 9 | var bitcore = require('qtumcore-lib'); 10 | var chai = require('chai'); 11 | var should = chai.should(); 12 | 13 | var index = require('..'); 14 | var log = index.log; 15 | log.debug = function() {}; 16 | var BitcoreNode = index.Node; 17 | var BitcoinService = index.services.Bitcoin; 18 | 19 | describe('Bitcoin Cluster', function() { 20 | var node; 21 | var daemons = []; 22 | var execPath = path.resolve(__dirname, '../bin/qtumd'); 23 | var nodesConf = [ 24 | { 25 | datadir: path.resolve(__dirname, './data/node1'), 26 | conf: path.resolve(__dirname, './data/node1/bitcoin.conf'), 27 | rpcuser: 'bitcoin', 28 | rpcpassword: 'local321', 29 | rpcport: 30521, 30 | zmqpubrawtx: 'tcp://127.0.0.1:30611', 31 | zmqpubhashblock: 'tcp://127.0.0.1:30611' 32 | }, 33 | { 34 | datadir: path.resolve(__dirname, './data/node2'), 35 | conf: path.resolve(__dirname, './data/node2/bitcoin.conf'), 36 | rpcuser: 'bitcoin', 37 | rpcpassword: 'local321', 38 | rpcport: 30522, 39 | zmqpubrawtx: 'tcp://127.0.0.1:30622', 40 | zmqpubhashblock: 'tcp://127.0.0.1:30622' 41 | }, 42 | { 43 | datadir: path.resolve(__dirname, './data/node3'), 44 | conf: path.resolve(__dirname, './data/node3/bitcoin.conf'), 45 | rpcuser: 'bitcoin', 46 | rpcpassword: 'local321', 47 | rpcport: 30523, 48 | zmqpubrawtx: 'tcp://127.0.0.1:30633', 49 | zmqpubhashblock: 'tcp://127.0.0.1:30633' 50 | } 51 | ]; 52 | 53 | before(function(done) { 54 | log.info('Starting 3 qtumd daemons'); 55 | this.timeout(60000); 56 | async.each(nodesConf, function(nodeConf, next) { 57 | var opts = [ 58 | '--regtest', 59 | '--datadir=' + nodeConf.datadir, 60 | '--conf=' + nodeConf.conf 61 | ]; 62 | 63 | rimraf(path.resolve(nodeConf.datadir, './regtest'), function(err) { 64 | if (err) { 65 | return done(err); 66 | } 67 | 68 | var process = spawn(execPath, opts, {stdio: 'inherit'}); 69 | 70 | var client = new BitcoinRPC({ 71 | protocol: 'http', 72 | host: '127.0.0.1', 73 | port: nodeConf.rpcport, 74 | user: nodeConf.rpcuser, 75 | pass: nodeConf.rpcpassword 76 | }); 77 | 78 | daemons.push(process); 79 | 80 | async.retry({times: 10, interval: 5000}, function(ready) { 81 | client.getInfo(ready); 82 | }, next); 83 | 84 | }); 85 | 86 | }, done); 87 | }); 88 | 89 | after(function(done) { 90 | this.timeout(10000); 91 | setTimeout(function() { 92 | async.each(daemons, function(process, next) { 93 | process.once('exit', next); 94 | process.kill('SIGINT'); 95 | }, done); 96 | }, 1000); 97 | }); 98 | 99 | it('step 1: will connect to three qtumd daemons', function(done) { 100 | this.timeout(20000); 101 | var configuration = { 102 | network: 'regtest', 103 | services: [ 104 | { 105 | name: 'qtumd', 106 | module: BitcoinService, 107 | config: { 108 | connect: [ 109 | { 110 | rpchost: '127.0.0.1', 111 | rpcport: 30521, 112 | rpcuser: 'qtumd', 113 | rpcpassword: 'local321', 114 | zmqpubrawtx: 'tcp://127.0.0.1:30611' 115 | }, 116 | { 117 | rpchost: '127.0.0.1', 118 | rpcport: 30522, 119 | rpcuser: 'bitcoin', 120 | rpcpassword: 'local321', 121 | zmqpubrawtx: 'tcp://127.0.0.1:30622' 122 | }, 123 | { 124 | rpchost: '127.0.0.1', 125 | rpcport: 30523, 126 | rpcuser: 'bitcoin', 127 | rpcpassword: 'local321', 128 | zmqpubrawtx: 'tcp://127.0.0.1:30633' 129 | } 130 | ] 131 | } 132 | } 133 | ] 134 | }; 135 | 136 | var regtest = bitcore.Networks.get('regtest'); 137 | should.exist(regtest); 138 | 139 | node = new BitcoreNode(configuration); 140 | 141 | node.on('error', function(err) { 142 | log.error(err); 143 | }); 144 | 145 | node.on('ready', function() { 146 | done(); 147 | }); 148 | 149 | node.start(function(err) { 150 | if (err) { 151 | return done(err); 152 | } 153 | }); 154 | 155 | }); 156 | 157 | it('step 2: receive block events', function(done) { 158 | this.timeout(10000); 159 | node.services.qtumd.once('tip', function(height) { 160 | height.should.equal(1); 161 | done(); 162 | }); 163 | node.generateBlock(1, function(err, hashes) { 164 | if (err) { 165 | return done(err); 166 | } 167 | should.exist(hashes); 168 | }); 169 | }); 170 | 171 | it('step 3: get blocks', function(done) { 172 | async.times(3, function(n, next) { 173 | node.getBlock(1, function(err, block) { 174 | if (err) { 175 | return next(err); 176 | } 177 | should.exist(block); 178 | next(); 179 | }); 180 | }, done); 181 | }); 182 | 183 | }); 184 | -------------------------------------------------------------------------------- /regtest/data/.gitignore: -------------------------------------------------------------------------------- 1 | .lock 2 | blocks 3 | regtest 4 | -------------------------------------------------------------------------------- /regtest/data/bitcoin.conf: -------------------------------------------------------------------------------- 1 | server=1 2 | whitelist=127.0.0.1 3 | txindex=1 4 | addressindex=1 5 | timestampindex=1 6 | spentindex=1 7 | zmqpubrawtx=tcp://127.0.0.1:30332 8 | zmqpubhashblock=tcp://127.0.0.1:30332 9 | rpcallowip=127.0.0.1 10 | rpcport=30331 11 | rpcuser=bitcoin 12 | rpcpassword=local321 13 | -------------------------------------------------------------------------------- /regtest/data/bitcoind.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICDTCCAXYCCQCsGf/7CM97gDANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCR0ExDDAKBgNVBAcTA2ZvbzEhMB8GA1UEChMYSW50ZXJuZXQg 4 | V2lkZ2l0cyBQdHkgTHRkMB4XDTE1MDgyNjE3NTAwOFoXDTE1MDkyNTE3NTAwOFow 5 | SzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkdBMQwwCgYDVQQHEwNmb28xITAfBgNV 6 | BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOB 7 | jQAwgYkCgYEA7SXnPpKk+0qXTTa42jzvu/C/gdGby0arY50bE2A+epI7FlI5YqKd 8 | hQWoNRpuehF3jH6Ij3mbeeImtTA7TaUYlgHKpn63xfJ0cRj55+6vqq09nDxf0Lm9 9 | IpTbgllu1l+SHtSuzFBVtGuNRSqObf8gD5XCD5lWK1vXHQ6PFSnAakMCAwEAATAN 10 | BgkqhkiG9w0BAQUFAAOBgQBNARLDgsw7NCBVkn57AEgwZptxeyvFWlGZCd0BmYIX 11 | ZFk7T1OQDwn7GlHry2IBswI0QRi076RQ4oJq+fg2O3XdFvEYV0cyypW7AxrnYTHP 12 | m1h2xr6Y5vhxFKP8DxpAxST27DHbR18YvTD+IGtp2UjLj646587N0MWxt8vmaU3c 13 | og== 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /regtest/data/bitcoind_no_pass.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXwIBAAKBgQDtJec+kqT7SpdNNrjaPO+78L+B0ZvLRqtjnRsTYD56kjsWUjli 3 | op2FBag1Gm56EXeMfoiPeZt54ia1MDtNpRiWAcqmfrfF8nRxGPnn7q+qrT2cPF/Q 4 | ub0ilNuCWW7WX5Ie1K7MUFW0a41FKo5t/yAPlcIPmVYrW9cdDo8VKcBqQwIDAQAB 5 | AoGBAOzYFxyCPu2OMI/4ICQuCcwtBEa2Ph+Fo/Rn2ru+OogV9Zc0ZYWiHSnWXYkz 6 | rbSSL1CMqvyIGoRfHgOFeSTxxxtVyRo5LayI6Ce6V04yUFua16uo6hMX5bfGKZ9d 7 | uq/HCDmdQvgifxUFpTpoSencuxwVCSYstMqjGfpobc5nxN2RAkEA+23BWNyA2qcq 8 | yqSplgTQO0laXox9ksr1k2mJB2HtG3GNs1kapP+Z8AVtn/Mf9Va0hgbSnqpF9fll 9 | 1xpBqfidSQJBAPF1rizAm7xP7AeRRET36YKjZCVayA/g4rp7kecrNJTTupJIuxHr 10 | JUlOTtXEWjIVcutSM7bP7bytPv4SAaApBysCQQDEZhqnCC+bHQO/IVrbNc1W0ljG 11 | DGY22VV1HfYND0CAtHXkx9CZXJPpusPEMs0e/uiq3P9/MzDNEFCt8vOiCvMJAkEA 12 | 65oDIKGzk/R7/wpMjetEyva5AgXpjizFrmZigCjVPp61voT/G8XQ9Q1WuRjFVXc+ 13 | UcU8tpV+iIqXG3vgYDGITwJBANb0NFFF8QsygbENtad1tw1C/hNabHk8n9hu+Z8+ 14 | OSzEMlP7SsvddPaqusGydhTUxazoG3s4kEh5WmCWKgZGKO0= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /regtest/data/node1/bitcoin.conf: -------------------------------------------------------------------------------- 1 | server=1 2 | whitelist=127.0.0.1 3 | txindex=1 4 | addressindex=1 5 | timestampindex=1 6 | spentindex=1 7 | addnode=127.0.0.1:30432 8 | addnode=127.0.0.1:30433 9 | port=30431 10 | rpcport=30521 11 | zmqpubrawtx=tcp://127.0.0.1:30611 12 | zmqpubhashblock=tcp://127.0.0.1:30611 13 | rpcallowip=127.0.0.1 14 | rpcuser=bitcoin 15 | rpcpassword=local321 16 | keypool=3 17 | -------------------------------------------------------------------------------- /regtest/data/node2/bitcoin.conf: -------------------------------------------------------------------------------- 1 | server=1 2 | whitelist=127.0.0.1 3 | txindex=1 4 | addressindex=1 5 | timestampindex=1 6 | spentindex=1 7 | addnode=127.0.0.1:30431 8 | addnode=127.0.0.1:30433 9 | port=30432 10 | rpcport=30522 11 | zmqpubrawtx=tcp://127.0.0.1:30622 12 | zmqpubhashblock=tcp://127.0.0.1:30622 13 | rpcallowip=127.0.0.1 14 | rpcuser=bitcoin 15 | rpcpassword=local321 16 | keypool=3 17 | -------------------------------------------------------------------------------- /regtest/data/node3/bitcoin.conf: -------------------------------------------------------------------------------- 1 | server=1 2 | whitelist=127.0.0.1 3 | txindex=1 4 | addressindex=1 5 | timestampindex=1 6 | spentindex=1 7 | addnode=127.0.0.1:30431 8 | addnode=127.0.0.1:30432 9 | port=30433 10 | rpcport=30523 11 | zmqpubrawtx=tcp://127.0.0.1:30633 12 | zmqpubhashblock=tcp://127.0.0.1:30633 13 | rpcallowip=127.0.0.1 14 | rpcuser=bitcoin 15 | rpcpassword=local321 16 | keypool=3 17 | -------------------------------------------------------------------------------- /regtest/data/qtum.conf: -------------------------------------------------------------------------------- 1 | server=1 2 | whitelist=127.0.0.1 3 | txindex=1 4 | addressindex=1 5 | timestampindex=1 6 | spentindex=1 7 | zmqpubrawtx=tcp://127.0.0.1:28332 8 | zmqpubhashblock=tcp://127.0.0.1:28332 9 | rpcallowip=127.0.0.1 10 | rpcuser=qtum 11 | rpcpassword=qtumpassword 12 | uacomment=qtumcore 13 | -------------------------------------------------------------------------------- /regtest/p2p.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // To run the tests: $ mocha -R spec regtest/p2p.js 4 | 5 | var path = require('path'); 6 | var index = require('..'); 7 | var log = index.log; 8 | 9 | var p2p = require('bitcore-p2p'); 10 | var Peer = p2p.Peer; 11 | var Messages = p2p.Messages; 12 | var chai = require('chai'); 13 | var bitcore = require('qtumcore-lib'); 14 | var Transaction = bitcore.Transaction; 15 | var BN = bitcore.crypto.BN; 16 | var async = require('async'); 17 | var rimraf = require('rimraf'); 18 | var qtumd; 19 | 20 | /* jshint unused: false */ 21 | var should = chai.should(); 22 | var assert = chai.assert; 23 | var sinon = require('sinon'); 24 | var BitcoinRPC = require('qtumd-rpc'); 25 | var transactionData = []; 26 | var blockHashes = []; 27 | var txs = []; 28 | var client; 29 | var messages; 30 | var peer; 31 | var coinbasePrivateKey; 32 | var privateKey = bitcore.PrivateKey(); 33 | var destKey = bitcore.PrivateKey(); 34 | var BufferUtil = bitcore.util.buffer; 35 | var blocks; 36 | 37 | describe('P2P Functionality', function() { 38 | 39 | before(function(done) { 40 | this.timeout(100000); 41 | 42 | // enable regtest 43 | bitcore.Networks.enableRegtest(); 44 | var regtestNetwork = bitcore.Networks.get('regtest'); 45 | var datadir = __dirname + '/data'; 46 | 47 | rimraf(datadir + '/regtest', function(err) { 48 | if (err) { 49 | throw err; 50 | } 51 | 52 | qtumd = require('../').services.Bitcoin({ 53 | spawn: { 54 | datadir: datadir, 55 | exec: path.resolve(__dirname, '../bin/qtumd') 56 | }, 57 | node: { 58 | network: bitcore.Networks.testnet 59 | } 60 | }); 61 | 62 | qtumd.on('error', function(err) { 63 | log.error('error="%s"', err.message); 64 | }); 65 | 66 | log.info('Waiting for Bitcoin Core to initialize...'); 67 | 68 | qtumd.start(function(err) { 69 | if (err) { 70 | throw err; 71 | } 72 | log.info('Bitcoind started'); 73 | 74 | client = new BitcoinRPC({ 75 | protocol: 'http', 76 | host: '127.0.0.1', 77 | port: 30331, 78 | user: 'bitcoin', 79 | pass: 'local321', 80 | rejectUnauthorized: false 81 | }); 82 | 83 | peer = new Peer({ 84 | host: '127.0.0.1', 85 | port: '18444', 86 | network: regtestNetwork 87 | }); 88 | 89 | messages = new Messages({ 90 | network: regtestNetwork 91 | }); 92 | 93 | blocks = 500; 94 | 95 | log.info('Generating ' + blocks + ' blocks...'); 96 | 97 | // Generate enough blocks so that the initial coinbase transactions 98 | // can be spent. 99 | 100 | setImmediate(function() { 101 | client.generate(blocks, function(err, response) { 102 | if (err) { 103 | throw err; 104 | } 105 | blockHashes = response.result; 106 | 107 | log.info('Preparing test data...'); 108 | 109 | // Get all of the unspent outputs 110 | client.listUnspent(0, blocks, function(err, response) { 111 | var utxos = response.result; 112 | 113 | async.mapSeries(utxos, function(utxo, next) { 114 | async.series([ 115 | function(finished) { 116 | // Load all of the transactions for later testing 117 | client.getTransaction(utxo.txid, function(err, txresponse) { 118 | if (err) { 119 | throw err; 120 | } 121 | // add to the list of transactions for testing later 122 | transactionData.push(txresponse.result.hex); 123 | finished(); 124 | }); 125 | }, 126 | function(finished) { 127 | // Get the private key for each utxo 128 | client.dumpPrivKey(utxo.address, function(err, privresponse) { 129 | if (err) { 130 | throw err; 131 | } 132 | utxo.privateKeyWIF = privresponse.result; 133 | var tx = bitcore.Transaction(); 134 | tx.from(utxo); 135 | tx.change(privateKey.toAddress()); 136 | tx.to(destKey.toAddress(), utxo.amount * 1e8 - 1000); 137 | tx.sign(bitcore.PrivateKey.fromWIF(utxo.privateKeyWIF)); 138 | txs.push(tx); 139 | finished(); 140 | }); 141 | } 142 | ], next); 143 | }, function(err) { 144 | if (err) { 145 | throw err; 146 | } 147 | peer.on('ready', function() { 148 | log.info('Peer ready'); 149 | done(); 150 | }); 151 | log.info('Connecting to peer'); 152 | peer.connect(); 153 | }); 154 | }); 155 | }); 156 | }); 157 | }); 158 | }); 159 | 160 | }); 161 | 162 | after(function(done) { 163 | this.timeout(20000); 164 | peer.on('disconnect', function() { 165 | log.info('Peer disconnected'); 166 | qtumd.node.stopping = true; 167 | qtumd.stop(function(err, result) { 168 | done(); 169 | }); 170 | }); 171 | peer.disconnect(); 172 | }); 173 | 174 | it('will be able to handle many inventory messages and be able to send getdata messages and received the txs', function(done) { 175 | this.timeout(100000); 176 | 177 | var usedTxs = {}; 178 | 179 | qtumd.on('tx', function(buffer) { 180 | var txFromResult = new Transaction().fromBuffer(buffer); 181 | var tx = usedTxs[txFromResult.id]; 182 | should.exist(tx); 183 | buffer.toString('hex').should.equal(tx.serialize()); 184 | delete usedTxs[tx.id]; 185 | if (Object.keys(usedTxs).length === 0) { 186 | done(); 187 | } 188 | }); 189 | 190 | peer.on('getdata', function(message) { 191 | var hash = message.inventory[0].hash; 192 | var reversedHash = BufferUtil.reverse(hash).toString('hex'); 193 | var tx = usedTxs[reversedHash]; 194 | if (reversedHash === tx.id) { 195 | var txMessage = messages.Transaction(tx); 196 | peer.sendMessage(txMessage); 197 | } 198 | }); 199 | async.whilst( 200 | function() { 201 | return txs.length > 0; 202 | }, 203 | function(callback) { 204 | var tx = txs.pop(); 205 | usedTxs[tx.id] = tx; 206 | var message = messages.Inventory.forTransaction(tx.hash); 207 | peer.sendMessage(message); 208 | callback(); 209 | }, 210 | function(err) { 211 | if (err) { 212 | throw err; 213 | } 214 | }); 215 | }); 216 | 217 | }); 218 | -------------------------------------------------------------------------------- /scripts/download: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.." 6 | platform=`uname -a | awk '{print tolower($1)}'` 7 | arch=`uname -m` 8 | version="0.12.1" 9 | url="https://github.com/bitpay/bitcoin/releases/download" 10 | tag="v0.12.1-bitcore-4" 11 | 12 | if [ "${platform}" == "linux" ]; then 13 | if [ "${arch}" == "x86_64" ]; then 14 | tarball_name="bitcoin-${version}-linux64.tar.gz" 15 | elif [ "${arch}" == "x86_32" ]; then 16 | tarball_name="bitcoin-${version}-linux32.tar.gz" 17 | fi 18 | elif [ "${platform}" == "darwin" ]; then 19 | tarball_name="bitcoin-${version}-osx64.tar.gz" 20 | else 21 | echo "Bitcoin binary distribution not available for platform and architecture" 22 | exit -1 23 | fi 24 | 25 | binary_url="${url}/${tag}/${tarball_name}" 26 | shasums_url="${url}/${tag}/SHA256SUMS.asc" 27 | 28 | download_bitcoind() { 29 | 30 | cd "${root_dir}/bin" 31 | 32 | echo "Downloading bitcoin: ${binary_url}" 33 | 34 | is_curl=true 35 | if hash curl 2>/dev/null; then 36 | curl --fail -I $binary_url >/dev/null 2>&1 37 | else 38 | is_curl=false 39 | wget --server-response --spider $binary_url >/dev/null 2>&1 40 | fi 41 | 42 | if test $? -eq 0; then 43 | if [ "${is_curl}" = true ]; then 44 | curl -L $binary_url > $tarball_name 45 | curl -L $shasums_url > SHA256SUMS.asc 46 | else 47 | wget $binary_url 48 | wget $shasums_url 49 | fi 50 | if test -e "${tarball_name}"; then 51 | echo "Unpacking bitcoin distribution" 52 | tar -xvzf $tarball_name 53 | if test $? -eq 0; then 54 | ln -sf "bitcoin-${version}/bin/bitcoind" 55 | return; 56 | fi 57 | fi 58 | fi 59 | echo "Bitcoin binary distribution could not be downloaded" 60 | exit -1 61 | } 62 | 63 | verify_download() { 64 | echo "Verifying signatures of bitcoin download" 65 | gpg --verify "${root_dir}/bin/SHA256SUMS.asc" 66 | 67 | if hash shasum 2>/dev/null; then 68 | shasum_cmd="shasum -a 256" 69 | else 70 | shasum_cmd="sha256sum" 71 | fi 72 | 73 | download_sha=$(${shasum_cmd} "${root_dir}/bin/${tarball_name}" | awk '{print $1}') 74 | expected_sha=$(cat "${root_dir}/bin/SHA256SUMS.asc" | grep "${tarball_name}" | awk '{print $1}') 75 | echo "Checksum (download): ${download_sha}" 76 | echo "Checksum (verified): ${expected_sha}" 77 | if [ "${download_sha}" != "${expected_sha}" ]; then 78 | echo -e "\033[1;31mChecksums did NOT match!\033[0m\n" 79 | exit 1 80 | else 81 | echo -e "\033[1;32mChecksums matched!\033[0m\n" 82 | fi 83 | } 84 | 85 | download=1 86 | verify=0 87 | 88 | if [ "${SKIP_BITCOIN_DOWNLOAD}" = 1 ]; then 89 | download=0; 90 | fi 91 | 92 | if [ "${VERIFY_BITCOIN_DOWNLOAD}" = 1 ]; then 93 | verify=1; 94 | fi 95 | 96 | while [ -n "$1" ]; do 97 | param="$1" 98 | value="$2" 99 | 100 | case $param in 101 | --skip-bitcoin-download) 102 | download=0 103 | ;; 104 | --verify-bitcoin-download) 105 | verify=1 106 | ;; 107 | esac 108 | shift 109 | done 110 | 111 | if [ "${download}" = 1 ]; then 112 | download_bitcoind 113 | fi 114 | 115 | if [ "${verify}" = 1 ]; then 116 | verify_download 117 | fi 118 | 119 | exit 0 120 | -------------------------------------------------------------------------------- /scripts/regtest: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | _mocha -R spec regtest/p2p.js 6 | _mocha -R spec regtest/bitcoind.js 7 | _mocha -R spec regtest/cluster.js 8 | _mocha -R spec regtest/node.js 9 | -------------------------------------------------------------------------------- /test/bus.integration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sinon = require('sinon'); 4 | var Service = require('../lib/service'); 5 | var QtumcoreNode = require('../lib/node'); 6 | var util = require('util'); 7 | var should = require('chai').should(); 8 | var index = require('../lib'); 9 | var log = index.log; 10 | 11 | var TestService = function(options) { 12 | this.node = options.node; 13 | }; 14 | util.inherits(TestService, Service); 15 | TestService.dependencies = []; 16 | 17 | TestService.prototype.start = function(callback) { 18 | callback(); 19 | }; 20 | TestService.prototype.stop = function(callback) { 21 | callback(); 22 | }; 23 | TestService.prototype.close = function(callback) { 24 | callback(); 25 | }; 26 | TestService.prototype.getPublishEvents = function() { 27 | return [ 28 | { 29 | name: 'test/testEvent', 30 | scope: this, 31 | subscribe: this.subscribe.bind(this, 'test/testEvent'), 32 | unsubscribe: this.unsubscribe.bind(this, 'test/testEvent') 33 | } 34 | ]; 35 | }; 36 | 37 | TestService.prototype.subscribe = function(name, emitter, params) { 38 | emitter.emit(name, params); 39 | }; 40 | 41 | TestService.prototype.unsubscribe = function(name, emitter) { 42 | emitter.emit('unsubscribe'); 43 | }; 44 | 45 | 46 | describe('Bus Functionality', function() { 47 | var sandbox = sinon.sandbox.create(); 48 | beforeEach(function() { 49 | sandbox.stub(log, 'info'); 50 | }); 51 | afterEach(function() { 52 | sandbox.restore(); 53 | }); 54 | 55 | it('should subscribe to testEvent', function(done) { 56 | var node = new QtumcoreNode({ 57 | datadir: './', 58 | network: 'testnet', 59 | port: 8888, 60 | services: [ 61 | { 62 | name: 'testService', 63 | config: {}, 64 | module: TestService 65 | } 66 | ] 67 | }); 68 | node.start(function() { 69 | var bus = node.openBus(); 70 | var params = 'somedata'; 71 | bus.on('test/testEvent', function(data) { 72 | data.should.be.equal(params); 73 | done(); 74 | }); 75 | bus.subscribe('test/testEvent', params); 76 | }); 77 | }); 78 | 79 | it('should unsubscribe from a testEvent', function(done) { 80 | var node = new QtumcoreNode({ 81 | datadir: './', 82 | network: 'testnet', 83 | port: 8888, 84 | services: [ 85 | { 86 | name: 'testService', 87 | config: {}, 88 | module: TestService 89 | } 90 | ] 91 | }); 92 | node.start(function() { 93 | var bus = node.openBus(); 94 | var params = 'somedata'; 95 | bus.on('unsubscribe', function() { 96 | done(); 97 | }); 98 | bus.subscribe('test/testEvent'); 99 | bus.unsubscribe('test/testEvent'); 100 | 101 | }); 102 | 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/bus.unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | var sinon = require('sinon'); 5 | var Bus = require('../lib/bus'); 6 | 7 | describe('Bus', function() { 8 | 9 | describe('#subscribe', function() { 10 | it('will call db and services subscribe function with the correct arguments', function() { 11 | var subscribeDb = sinon.spy(); 12 | var subscribeService = sinon.spy(); 13 | var node = { 14 | services: { 15 | db: { 16 | getPublishEvents: sinon.stub().returns([ 17 | { 18 | name: 'dbtest', 19 | scope: this, 20 | subscribe: subscribeDb 21 | } 22 | ]) 23 | }, 24 | service1: { 25 | getPublishEvents: sinon.stub().returns([ 26 | { 27 | name: 'test', 28 | scope: this, 29 | subscribe: subscribeService, 30 | } 31 | ]) 32 | } 33 | } 34 | }; 35 | var bus = new Bus({node: node}); 36 | bus.subscribe('dbtest', 'a', 'b', 'c'); 37 | bus.subscribe('test', 'a', 'b', 'c'); 38 | subscribeService.callCount.should.equal(1); 39 | subscribeDb.callCount.should.equal(1); 40 | subscribeDb.args[0][0].should.equal(bus); 41 | subscribeDb.args[0][1].should.equal('a'); 42 | subscribeDb.args[0][2].should.equal('b'); 43 | subscribeDb.args[0][3].should.equal('c'); 44 | subscribeService.args[0][0].should.equal(bus); 45 | subscribeService.args[0][1].should.equal('a'); 46 | subscribeService.args[0][2].should.equal('b'); 47 | subscribeService.args[0][3].should.equal('c'); 48 | }); 49 | }); 50 | 51 | describe('#unsubscribe', function() { 52 | it('will call db and services unsubscribe function with the correct arguments', function() { 53 | var unsubscribeDb = sinon.spy(); 54 | var unsubscribeService = sinon.spy(); 55 | var node = { 56 | services: { 57 | db: { 58 | getPublishEvents: sinon.stub().returns([ 59 | { 60 | name: 'dbtest', 61 | scope: this, 62 | unsubscribe: unsubscribeDb 63 | } 64 | ]) 65 | }, 66 | service1: { 67 | getPublishEvents: sinon.stub().returns([ 68 | { 69 | name: 'test', 70 | scope: this, 71 | unsubscribe: unsubscribeService, 72 | } 73 | ]) 74 | } 75 | } 76 | }; 77 | var bus = new Bus({node: node}); 78 | bus.unsubscribe('dbtest', 'a', 'b', 'c'); 79 | bus.unsubscribe('test', 'a', 'b', 'c'); 80 | unsubscribeService.callCount.should.equal(1); 81 | unsubscribeDb.callCount.should.equal(1); 82 | unsubscribeDb.args[0][0].should.equal(bus); 83 | unsubscribeDb.args[0][1].should.equal('a'); 84 | unsubscribeDb.args[0][2].should.equal('b'); 85 | unsubscribeDb.args[0][3].should.equal('c'); 86 | unsubscribeService.args[0][0].should.equal(bus); 87 | unsubscribeService.args[0][1].should.equal('a'); 88 | unsubscribeService.args[0][2].should.equal('b'); 89 | unsubscribeService.args[0][3].should.equal('c'); 90 | }); 91 | }); 92 | 93 | describe('#close', function() { 94 | it('will unsubscribe from all events', function() { 95 | var unsubscribeDb = sinon.spy(); 96 | var unsubscribeService = sinon.spy(); 97 | var node = { 98 | services: { 99 | db: { 100 | getPublishEvents: sinon.stub().returns([ 101 | { 102 | name: 'dbtest', 103 | scope: this, 104 | unsubscribe: unsubscribeDb 105 | } 106 | ]) 107 | }, 108 | service1: { 109 | getPublishEvents: sinon.stub().returns([ 110 | { 111 | name: 'test', 112 | scope: this, 113 | unsubscribe: unsubscribeService 114 | } 115 | ]) 116 | } 117 | } 118 | }; 119 | var bus = new Bus({node: node}); 120 | bus.close(); 121 | 122 | unsubscribeDb.callCount.should.equal(1); 123 | unsubscribeService.callCount.should.equal(1); 124 | unsubscribeDb.args[0].length.should.equal(1); 125 | unsubscribeDb.args[0][0].should.equal(bus); 126 | unsubscribeService.args[0].length.should.equal(1); 127 | unsubscribeService.args[0][0].should.equal(bus); 128 | }); 129 | }); 130 | 131 | }); 132 | -------------------------------------------------------------------------------- /test/data/badqtum.conf: -------------------------------------------------------------------------------- 1 | #testnet=1 2 | #irc=0 3 | #upnp=0 4 | server=1 5 | 6 | whitelist=127.0.0.1 7 | 8 | # listen on different ports 9 | port=20000 10 | 11 | rpcallowip=127.0.0.1 12 | 13 | rpcuser=qtum 14 | rpcpassword=qtumpassword 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/data/bitcoin-transactions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "comment": "sends to tx[1]", 4 | "hex":"0100000001dee8f4266e83072e0ad258125cc5a42ac25d2d2c73e6e2e873413b3939af1605000000006b483045022100ae987d056f81d2c982b71b0406f2374c1958b24bd289d77371347e275d2a62c002205148b17173be18af4e1e73ce2b0fd600734ea77087754bdba5dc7d645b01880a01210226ab3b46f85bf32f63778c680e16ef8b3fcb51d638a7980d651bfaeae6c17752ffffffff0170820300000000001976a9142baf68e3681df183375a4f4c10306de9a5c6cc7788ac00000000" 5 | }, 6 | { 7 | "comment": "spends from tx[0] (valid)", 8 | "hex":"0100000001f77c71cf8c272d22471f054cae7fb48561ebcf004b8ec8f9f65cd87af82a2944000000006a47304402203c2bc91a170facdc5ef4b5b94c413bc7a10f65e09b326d205f070b17aa94d67102205b684111af2a20171eb65db73e6c73f9e77e6e6f739e050bc052ed6ecc9feb4a01210365d8756a4f3fc738105cfab8d80a85189bdb4db5af83374e645b79e2aadd976effffffff01605b0300000000001976a9149e84d1295471958e5ffccd8d36a57bd5d220f8ed88ac00000000" 9 | }, 10 | { 11 | "comment": "spends from tx[0] (missing signature)", 12 | "hex":"0100000001f77c71cf8c272d22471f054cae7fb48561ebcf004b8ec8f9f65cd87af82a29440000000000ffffffff01605b0300000000001976a9149e84d1295471958e5ffccd8d36a57bd5d220f8ed88ac00000000" 13 | } 14 | ] -------------------------------------------------------------------------------- /test/data/default.qtum.conf: -------------------------------------------------------------------------------- 1 | server=1 2 | whitelist=127.0.0.1 3 | txindex=1 4 | addressindex=1 5 | timestampindex=1 6 | spentindex=1 7 | zmqpubrawtx=tcp://127.0.0.1:28332 8 | zmqpubhashblock=tcp://127.0.0.1:28332 9 | rpcallowip=127.0.0.1 10 | rpcuser=qtum 11 | rpcpassword=qtumpassword 12 | uacomment=bitcore 13 | -------------------------------------------------------------------------------- /test/data/qtum.conf: -------------------------------------------------------------------------------- 1 | #testnet=1 2 | #irc=0 3 | upnp=0 4 | server=1 5 | 6 | whitelist=127.0.0.1 7 | txindex=1 8 | addressindex=1 9 | timestampindex=1 10 | spentindex=1 11 | dbcache=8192 12 | checkblocks=144 13 | maxuploadtarget=1024 14 | zmqpubrawtx=tcp://127.0.0.1:28332 15 | zmqpubhashblock=tcp://127.0.0.1:28332 16 | 17 | port=20000 18 | rpcport=50001 19 | 20 | rpcallowip=127.0.0.1 21 | 22 | rpcuser=qtum 23 | rpcpassword=qtumpassword 24 | -------------------------------------------------------------------------------- /test/data/transaction.json: -------------------------------------------------------------------------------- 1 | [ 2 | "010000000ec9e1d96d51f7d0a5b726184cda744207e51a596f13b564109de6ffc0653055cf000000006a4730440220263d48a2f4c3a2aa6032f96253c853531131171d8ae3d30586a45de7ba7c4006022077db4b39926877939baf59e8d357effe7674e7b12ad986c0a4cd99f2b7acafca012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff0c16800642bdf90cbbd340be2e801bee9b907db6d59dc4c7cb269358c1e2593a000000006a4730440220085e39cb3a948559c1b1c88ba635eeef37a767322c1e4d96556e998302acb5dc0220421c03de78121692c538a9bc85a58fbe796384541fe87b6e91c39404318c390d012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff559fde3733e950db9e3faea1a27872047a5c8bc14e8e6ac4a7b4f7b5f90c42c2000000006b483045022100a46a109a5acfc34b13c591ab69392e2dc2c3ea12d0900c5f7a539ea888e57ae0022015372bad56d63c6d08a5e66e0c9a63b2fc8dce245aa765e37dac06acb84c18d501210207e2a01d4a334c2d7c9ebacc5ea8a0d4b86fd54599b1aebe125d9e664be012c2ffffffff92760c236f6f18751c4ecab1dfcfebac7358a431b31bcd72b0a09d336233bdce000000006a47304402200857fa82e5b287c6ed5624cbc4fcd068a756a7a9ef785a73ce4e51b15a8aa34b022042cbc6f478b711539c6345d0b05d4bc9a9b5c34b2e4d25f234cf6784ff2eed19012103cab1f64f3d5f20a3f4a070694e6f93f5248b502b3842e369d81f05d07dec01e3ffffffff158669557e8a0b71288df37626601e636c5a8b3797f7f3357590313e8efe790a000000006b48304502210085e62cb95066540730b74aeae133277e511b5bf79de8c0ad60e784638e681ddf022043b456285569e0da133f527412218893f05a781d6f77f8cddd12eb90bfdc5937012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffffc1f9f57f1eda20f3b45669b3f0d1eae73b410ddf2b4fc1cfe10051a6051eff68000000006a47304402200fbe15c73446309040f4264567d0e8cc46691cf5d0626c443fc2dde716211e5402207f84e68e273755d140f346e029213dbc42b65cfb1e2ac41f72402c7ff45feffc012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff39fa27e16c540a9bb796e65d4ac624fc33878e522461d6955764bf7b83c03ce8000000006a4730440220131e6aed76da389f21dfd7cd271fad73c5c533b1c18fbfabd6799a0d0e7dc80602205c38855bea0f1dfbbb608bc1b56c75b91a39980de121ffd9f880b522d412d821012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff375fb7ae26eb637ccc007a669b9f50ed10fa53127504b80e0fd2be34c50becd7000000006b483045022100f0a9e585aa3113eae4bfb204f38f96e60dc613c04313aae931b677e4d8d7081d022014664874859f3d47447c6c0f257c28c74e8fdaedd5f781d752f3a4b651d3d179012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff6f9d6a3c847e6112bb920424ca0d8d6e0956b128acb206e8fb58b2d2f2d7d46b000000006a4730440220736b198484cf5226616a539146e037a93cc75963885eefe58fc29a7be8123c750220619a456c0fe7437ec67c642d88e890344fc1c51a7b3cfc0ae283d61d0f176c5e012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff3cccbd8090d60fcf28064333cf2f51ef0f838ba5e26a5e0f38807ee16d38a649000000006b483045022100e1ed25e9365e596d4fc3cbf278490d8ea765c4266c55f19311cf5da33f8d00750220361888a1738ebba827c0c947690b5f2a5f20e9f1be8956c3a34a4ba03f9e60f5012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff7f4d60a2e961490aa465a7df461bf09b151bdc0c162f3bef0c1cbed6160d02c7000000006a47304402204e79b15a1db9a356f00dc9f2d559e31561cad1343ba5809a65b52bd868e3963e022055b9326ed5de9aa9970ec67a2ebf1a9dbf9ee513b64bd13837c87320bb4d6947012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffffe63b9370ba49a129e071750cbb300128015fdd90d7399f9c4e44934eabbaa2f7000000006b483045022100b9ceb2e376c0334d45bf08bfeb06dc250e7cb01d3a08e2fb3506388683552417022024c7c5bda385b904cca691fb6e1ad8c5eba5858a88a2112cb824dca72793b7a7012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffffc78b96fddededb6cbc1dff9de51f2743fd42e91de2506794b121928af4729528000000006a47304402201f05caddee5a0ff590b27c4ce25be1cbbeb45dc39679a1b8b0e10b1a378d84bc02203e31b01e14d891e9809c43a4df54494c626c5e47eaeeeb99ab4e02bd73c3d6cd012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff30093916240e981a77cb869924fa0c38a894c24b1a6e7d26b117bb9caa7d5bbe000000006a4730440220483f297379daacee14babbf929708a861a991373bca3ed4eef240e2c156a162602205f1e93e375a897c6a9ddc3dc616ccf14137096ebd7888040e1053a769d21b945012103e9100732bb534bea2f6d3b971914ec8403557306600c01c5ce63ec185737c732ffffffff022897d411000000001976a91415354ee1828ed12f243f80dcb92c3a8205285f0188ac3be68c02000000001976a9143ecf8ff79932c3de33829a001236985872d10be188ac00000000" 3 | ] 4 | -------------------------------------------------------------------------------- /test/index.unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | 5 | describe('Index Exports', function() { 6 | it('will export qtumcore-lib', function() { 7 | var qtumcore = require('../'); 8 | should.exist(qtumcore.lib); 9 | should.exist(qtumcore.lib.Transaction); 10 | should.exist(qtumcore.lib.Block); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/logger.unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sinon = require('sinon'); 4 | var chai = require('chai'); 5 | var should = chai.should(); 6 | var Logger = require('../lib/logger'); 7 | 8 | describe('Logger', function() { 9 | var sandbox = sinon.sandbox.create(); 10 | afterEach(function() { 11 | sandbox.restore(); 12 | }); 13 | 14 | it('will instatiate without options', function() { 15 | var logger = new Logger(); 16 | should.exist(logger); 17 | logger.formatting.should.equal(true); 18 | }); 19 | 20 | it('will instatiate with formatting option', function() { 21 | var logger = new Logger({ 22 | formatting: false 23 | }); 24 | logger.formatting.should.equal(false); 25 | var logger2 = new Logger({ 26 | formatting: true 27 | }); 28 | logger2.formatting.should.equal(true); 29 | }); 30 | 31 | it('will log with formatting', function() { 32 | var logger = new Logger({formatting: true}); 33 | 34 | sandbox.stub(console, 'info'); 35 | logger.info('Test info log'); 36 | console.info.callCount.should.equal(1); 37 | console.info.restore(); 38 | 39 | sandbox.stub(console, 'error'); 40 | logger.error(new Error('Test error log')); 41 | console.error.callCount.should.equal(1); 42 | console.error.restore(); 43 | 44 | sandbox.stub(console, 'debug'); 45 | logger.debug('Test debug log'); 46 | console.debug.callCount.should.equal(1); 47 | console.debug.restore(); 48 | 49 | sandbox.stub(console, 'warn'); 50 | logger.warn('Test warn log'); 51 | console.warn.callCount.should.equal(1); 52 | console.warn.restore(); 53 | }); 54 | 55 | it('will log without formatting', function() { 56 | var logger = new Logger({formatting: false}); 57 | 58 | sandbox.stub(console, 'info'); 59 | logger.info('Test info log'); 60 | console.info.callCount.should.equal(1); 61 | should.not.exist(console.info.args[0][0].match(/^\[/)); 62 | console.info.restore(); 63 | 64 | sandbox.stub(console, 'error'); 65 | logger.error(new Error('Test error log')); 66 | console.error.callCount.should.equal(1); 67 | console.error.args[0][0].should.be.instanceof(Error); 68 | console.error.restore(); 69 | 70 | sandbox.stub(console, 'debug'); 71 | logger.debug('Test debug log'); 72 | console.debug.callCount.should.equal(1); 73 | should.equal(console.debug.args[0][0].match(/^\[/), null); 74 | console.debug.restore(); 75 | 76 | sandbox.stub(console, 'warn'); 77 | logger.warn('Test warn log'); 78 | console.warn.callCount.should.equal(1); 79 | should.equal(console.warn.args[0][0].match(/^\[/), null); 80 | console.warn.restore(); 81 | }); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /test/node.unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | var sinon = require('sinon'); 5 | var qtumcore = require('qtumcore-lib'); 6 | var Networks = qtumcore.Networks; 7 | var proxyquire = require('proxyquire'); 8 | var util = require('util'); 9 | var BaseService = require('../lib/service'); 10 | var index = require('../lib'); 11 | var log = index.log; 12 | 13 | describe('Qtumcore Node', function() { 14 | 15 | var baseConfig = {}; 16 | 17 | var Node; 18 | 19 | before(function() { 20 | Node = proxyquire('../lib/node', {}); 21 | Node.prototype._loadConfiguration = sinon.spy(); 22 | Node.prototype._initialize = sinon.spy(); 23 | }); 24 | 25 | after(function() { 26 | Networks.disableRegtest(); 27 | }); 28 | 29 | describe('@constructor', function() { 30 | var TestService; 31 | before(function() { 32 | TestService = function TestService() {}; 33 | util.inherits(TestService, BaseService); 34 | }); 35 | it('will set properties', function() { 36 | var config = { 37 | services: [ 38 | { 39 | name: 'test1', 40 | module: TestService 41 | } 42 | ], 43 | }; 44 | var TestNode = proxyquire('../lib/node', {}); 45 | TestNode.prototype.start = sinon.spy(); 46 | var node = new TestNode(config); 47 | node._unloadedServices.length.should.equal(1); 48 | node._unloadedServices[0].name.should.equal('test1'); 49 | node._unloadedServices[0].module.should.equal(TestService); 50 | node.network.should.equal(Networks.defaultNetwork); 51 | var node2 = TestNode(config); 52 | node2._unloadedServices.length.should.equal(1); 53 | node2._unloadedServices[0].name.should.equal('test1'); 54 | node2._unloadedServices[0].module.should.equal(TestService); 55 | node2.network.should.equal(Networks.defaultNetwork); 56 | }); 57 | it('will set network to testnet', function() { 58 | var config = { 59 | network: 'testnet', 60 | services: [ 61 | { 62 | name: 'test1', 63 | module: TestService 64 | } 65 | ], 66 | }; 67 | var TestNode = proxyquire('../lib/node', {}); 68 | TestNode.prototype.start = sinon.spy(); 69 | var node = new TestNode(config); 70 | node.network.should.equal(Networks.testnet); 71 | }); 72 | it('will set network to regtest', function() { 73 | var config = { 74 | network: 'regtest', 75 | services: [ 76 | { 77 | name: 'test1', 78 | module: TestService 79 | } 80 | ], 81 | }; 82 | var TestNode = proxyquire('../lib/node', {}); 83 | TestNode.prototype.start = sinon.spy(); 84 | var node = new TestNode(config); 85 | var regtest = Networks.get('regtest'); 86 | should.exist(regtest); 87 | node.network.should.equal(regtest); 88 | }); 89 | it('will be able to disable log formatting', function() { 90 | var config = { 91 | network: 'regtest', 92 | services: [ 93 | { 94 | name: 'test1', 95 | module: TestService 96 | } 97 | ], 98 | formatLogs: false 99 | }; 100 | var TestNode = proxyquire('../lib/node', {}); 101 | var node = new TestNode(config); 102 | node.log.formatting.should.equal(false); 103 | 104 | var TestNode = proxyquire('../lib/node', {}); 105 | config.formatLogs = true; 106 | var node2 = new TestNode(config); 107 | node2.log.formatting.should.equal(true); 108 | }); 109 | }); 110 | 111 | describe('#openBus', function() { 112 | it('will create a new bus', function() { 113 | var node = new Node(baseConfig); 114 | var bus = node.openBus(); 115 | bus.node.should.equal(node); 116 | }); 117 | it('will use remoteAddress config option', function() { 118 | var node = new Node(baseConfig); 119 | var bus = node.openBus({remoteAddress: '127.0.0.1'}); 120 | bus.remoteAddress.should.equal('127.0.0.1'); 121 | }); 122 | }); 123 | 124 | describe('#getAllAPIMethods', function() { 125 | it('should return db methods and service methods', function() { 126 | var node = new Node(baseConfig); 127 | node.services = { 128 | db: { 129 | getAPIMethods: sinon.stub().returns(['db1', 'db2']), 130 | }, 131 | service1: { 132 | getAPIMethods: sinon.stub().returns(['mda1', 'mda2']) 133 | }, 134 | service2: { 135 | getAPIMethods: sinon.stub().returns(['mdb1', 'mdb2']) 136 | } 137 | }; 138 | 139 | var methods = node.getAllAPIMethods(); 140 | methods.should.deep.equal(['db1', 'db2', 'mda1', 'mda2', 'mdb1', 'mdb2']); 141 | }); 142 | it('will handle service without getAPIMethods defined', function() { 143 | var node = new Node(baseConfig); 144 | node.services = { 145 | db: { 146 | getAPIMethods: sinon.stub().returns(['db1', 'db2']), 147 | }, 148 | service1: {}, 149 | service2: { 150 | getAPIMethods: sinon.stub().returns(['mdb1', 'mdb2']) 151 | } 152 | }; 153 | 154 | var methods = node.getAllAPIMethods(); 155 | methods.should.deep.equal(['db1', 'db2', 'mdb1', 'mdb2']); 156 | }); 157 | }); 158 | 159 | describe('#getAllPublishEvents', function() { 160 | it('should return services publish events', function() { 161 | var node = new Node(baseConfig); 162 | node.services = { 163 | db: { 164 | getPublishEvents: sinon.stub().returns(['db1', 'db2']), 165 | }, 166 | service1: { 167 | getPublishEvents: sinon.stub().returns(['mda1', 'mda2']) 168 | }, 169 | service2: { 170 | getPublishEvents: sinon.stub().returns(['mdb1', 'mdb2']) 171 | } 172 | }; 173 | var events = node.getAllPublishEvents(); 174 | events.should.deep.equal(['db1', 'db2', 'mda1', 'mda2', 'mdb1', 'mdb2']); 175 | }); 176 | it('will handle service without getPublishEvents defined', function() { 177 | var node = new Node(baseConfig); 178 | node.services = { 179 | db: { 180 | getPublishEvents: sinon.stub().returns(['db1', 'db2']), 181 | }, 182 | service1: {}, 183 | service2: { 184 | getPublishEvents: sinon.stub().returns(['mdb1', 'mdb2']) 185 | } 186 | }; 187 | var events = node.getAllPublishEvents(); 188 | events.should.deep.equal(['db1', 'db2', 'mdb1', 'mdb2']); 189 | }); 190 | }); 191 | 192 | describe('#getServiceOrder', function() { 193 | it('should return the services in the correct order', function() { 194 | var node = new Node(baseConfig); 195 | node._unloadedServices = [ 196 | { 197 | name: 'chain', 198 | module: { 199 | dependencies: ['db'] 200 | } 201 | }, 202 | { 203 | name: 'db', 204 | module: { 205 | dependencies: ['daemon', 'p2p'] 206 | } 207 | }, 208 | { 209 | name:'daemon', 210 | module: { 211 | dependencies: [] 212 | } 213 | }, 214 | { 215 | name: 'p2p', 216 | module: { 217 | dependencies: [] 218 | } 219 | } 220 | ]; 221 | var order = node.getServiceOrder(); 222 | order[0].name.should.equal('daemon'); 223 | order[1].name.should.equal('p2p'); 224 | order[2].name.should.equal('db'); 225 | order[3].name.should.equal('chain'); 226 | }); 227 | }); 228 | 229 | describe('#_startService', function() { 230 | var sandbox = sinon.sandbox.create(); 231 | beforeEach(function() { 232 | sandbox.stub(log, 'info'); 233 | }); 234 | afterEach(function() { 235 | sandbox.restore(); 236 | }); 237 | it('will instantiate an instance and load api methods', function() { 238 | var node = new Node(baseConfig); 239 | function TestService() {} 240 | util.inherits(TestService, BaseService); 241 | TestService.prototype.start = sinon.stub().callsArg(0); 242 | var getData = sinon.stub(); 243 | TestService.prototype.getData = getData; 244 | TestService.prototype.getAPIMethods = function() { 245 | return [ 246 | ['getData', this, this.getData, 1] 247 | ]; 248 | }; 249 | var service = { 250 | name: 'testservice', 251 | module: TestService, 252 | config: {} 253 | }; 254 | node._startService(service, function(err) { 255 | if (err) { 256 | throw err; 257 | } 258 | TestService.prototype.start.callCount.should.equal(1); 259 | should.exist(node.services.testservice); 260 | should.exist(node.getData); 261 | node.getData(); 262 | getData.callCount.should.equal(1); 263 | }); 264 | }); 265 | it('will handle config not being set', function() { 266 | var node = new Node(baseConfig); 267 | function TestService() {} 268 | util.inherits(TestService, BaseService); 269 | TestService.prototype.start = sinon.stub().callsArg(0); 270 | var getData = sinon.stub(); 271 | TestService.prototype.getData = getData; 272 | TestService.prototype.getAPIMethods = function() { 273 | return [ 274 | ['getData', this, this.getData, 1] 275 | ]; 276 | }; 277 | var service = { 278 | name: 'testservice', 279 | module: TestService, 280 | }; 281 | node._startService(service, function(err) { 282 | if (err) { 283 | throw err; 284 | } 285 | TestService.prototype.start.callCount.should.equal(1); 286 | should.exist(node.services.testservice); 287 | should.exist(node.getData); 288 | node.getData(); 289 | getData.callCount.should.equal(1); 290 | }); 291 | }); 292 | it('will give an error from start', function() { 293 | var node = new Node(baseConfig); 294 | function TestService() {} 295 | util.inherits(TestService, BaseService); 296 | TestService.prototype.start = sinon.stub().callsArgWith(0, new Error('test')); 297 | var service = { 298 | name: 'testservice', 299 | module: TestService, 300 | config: {} 301 | }; 302 | node._startService(service, function(err) { 303 | err.message.should.equal('test'); 304 | }); 305 | }); 306 | }); 307 | 308 | describe('#start', function() { 309 | var sandbox = sinon.sandbox.create(); 310 | beforeEach(function() { 311 | sandbox.stub(log, 'info'); 312 | }); 313 | afterEach(function() { 314 | sandbox.restore(); 315 | }); 316 | it('will call start for each service', function(done) { 317 | var node = new Node(baseConfig); 318 | 319 | function TestService() {} 320 | util.inherits(TestService, BaseService); 321 | TestService.prototype.start = sinon.stub().callsArg(0); 322 | TestService.prototype.getData = function() {}; 323 | TestService.prototype.getAPIMethods = function() { 324 | return [ 325 | ['getData', this, this.getData, 1] 326 | ]; 327 | }; 328 | 329 | function TestService2() {} 330 | util.inherits(TestService2, BaseService); 331 | TestService2.prototype.start = sinon.stub().callsArg(0); 332 | TestService2.prototype.getData2 = function() {}; 333 | TestService2.prototype.getAPIMethods = function() { 334 | return [ 335 | ['getData2', this, this.getData2, 1] 336 | ]; 337 | }; 338 | 339 | node.getServiceOrder = sinon.stub().returns([ 340 | { 341 | name: 'test1', 342 | module: TestService, 343 | config: {} 344 | }, 345 | { 346 | name: 'test2', 347 | module: TestService2, 348 | config: {} 349 | } 350 | ]); 351 | node.start(function() { 352 | TestService2.prototype.start.callCount.should.equal(1); 353 | TestService.prototype.start.callCount.should.equal(1); 354 | should.exist(node.getData2); 355 | should.exist(node.getData); 356 | done(); 357 | }); 358 | }); 359 | it('will error if there are conflicting API methods', function(done) { 360 | var node = new Node(baseConfig); 361 | 362 | function TestService() {} 363 | util.inherits(TestService, BaseService); 364 | TestService.prototype.start = sinon.stub().callsArg(0); 365 | TestService.prototype.getData = function() {}; 366 | TestService.prototype.getAPIMethods = function() { 367 | return [ 368 | ['getData', this, this.getData, 1] 369 | ]; 370 | }; 371 | 372 | function ConflictService() {} 373 | util.inherits(ConflictService, BaseService); 374 | ConflictService.prototype.start = sinon.stub().callsArg(0); 375 | ConflictService.prototype.getData = function() {}; 376 | ConflictService.prototype.getAPIMethods = function() { 377 | return [ 378 | ['getData', this, this.getData, 1] 379 | ]; 380 | }; 381 | 382 | node.getServiceOrder = sinon.stub().returns([ 383 | { 384 | name: 'test', 385 | module: TestService, 386 | config: {} 387 | }, 388 | { 389 | name: 'conflict', 390 | module: ConflictService, 391 | config: {} 392 | } 393 | ]); 394 | 395 | node.start(function(err) { 396 | should.exist(err); 397 | err.message.should.match(/^Existing API method\(s\) exists\:/); 398 | done(); 399 | }); 400 | 401 | }); 402 | it('will handle service with getAPIMethods undefined', function(done) { 403 | var node = new Node(baseConfig); 404 | 405 | function TestService() {} 406 | util.inherits(TestService, BaseService); 407 | TestService.prototype.start = sinon.stub().callsArg(0); 408 | TestService.prototype.getData = function() {}; 409 | 410 | node.getServiceOrder = sinon.stub().returns([ 411 | { 412 | name: 'test', 413 | module: TestService, 414 | config: {} 415 | }, 416 | ]); 417 | 418 | node.start(function() { 419 | TestService.prototype.start.callCount.should.equal(1); 420 | done(); 421 | }); 422 | 423 | }); 424 | }); 425 | 426 | describe('#getNetworkName', function() { 427 | afterEach(function() { 428 | qtumcore.Networks.disableRegtest(); 429 | }); 430 | it('it will return the network name for livenet', function() { 431 | var node = new Node(baseConfig); 432 | node.getNetworkName().should.equal('livenet'); 433 | }); 434 | it('it will return the network name for testnet', function() { 435 | var baseConfig = { 436 | network: 'testnet' 437 | }; 438 | var node = new Node(baseConfig); 439 | node.getNetworkName().should.equal('testnet'); 440 | }); 441 | it('it will return the network for regtest', function() { 442 | var baseConfig = { 443 | network: 'regtest' 444 | }; 445 | var node = new Node(baseConfig); 446 | node.getNetworkName().should.equal('regtest'); 447 | }); 448 | }); 449 | 450 | describe('#stop', function() { 451 | var sandbox = sinon.sandbox.create(); 452 | beforeEach(function() { 453 | sandbox.stub(log, 'info'); 454 | }); 455 | afterEach(function() { 456 | sandbox.restore(); 457 | }); 458 | it('will call stop for each service', function(done) { 459 | var node = new Node(baseConfig); 460 | function TestService() {} 461 | util.inherits(TestService, BaseService); 462 | TestService.prototype.stop = sinon.stub().callsArg(0); 463 | TestService.prototype.getData = function() {}; 464 | TestService.prototype.getAPIMethods = function() { 465 | return [ 466 | ['getData', this, this.getData, 1] 467 | ]; 468 | }; 469 | node.services = { 470 | 'test1': new TestService({node: node}) 471 | }; 472 | node.test2 = {}; 473 | node.test2.stop = sinon.stub().callsArg(0); 474 | node.getServiceOrder = sinon.stub().returns([ 475 | { 476 | name: 'test1', 477 | module: TestService 478 | } 479 | ]); 480 | node.stop(function() { 481 | TestService.prototype.stop.callCount.should.equal(1); 482 | done(); 483 | }); 484 | }); 485 | }); 486 | }); 487 | -------------------------------------------------------------------------------- /test/scaffold/add.integration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | var sinon = require('sinon'); 5 | var path = require('path'); 6 | var fs = require('fs'); 7 | var proxyquire = require('proxyquire'); 8 | var mkdirp = require('mkdirp'); 9 | var rimraf = require('rimraf'); 10 | var add = require('../../lib/scaffold/add'); 11 | 12 | describe('#add', function() { 13 | 14 | var basePath = path.resolve(__dirname, '..'); 15 | var testDir = path.resolve(basePath, 'temporary-test-data'); 16 | var startConfig = { 17 | name: 'My Node', 18 | services: [] 19 | }; 20 | var startPackage = {}; 21 | 22 | before(function(done) { 23 | mkdirp(testDir + '/s0/s1', function(err) { 24 | if (err) { 25 | throw err; 26 | } 27 | fs.writeFile( 28 | testDir + '/s0/s1/qtumcore-node.json', 29 | JSON.stringify(startConfig), 30 | function(err) { 31 | if (err) { 32 | throw err; 33 | } 34 | fs.writeFile( 35 | testDir + '/s0/s1/package.json', 36 | JSON.stringify(startPackage), 37 | done 38 | ); 39 | } 40 | ); 41 | }); 42 | }); 43 | 44 | after(function(done) { 45 | // cleanup testing directories 46 | rimraf(testDir, function(err) { 47 | if (err) { 48 | throw err; 49 | } 50 | done(); 51 | }); 52 | }); 53 | 54 | describe('will modify scaffold files', function() { 55 | 56 | it('will give an error if expected files do not exist', function(done) { 57 | add({ 58 | path: path.resolve(testDir, 's0'), 59 | services: ['a', 'b', 'c'] 60 | }, function(err) { 61 | should.exist(err); 62 | err.message.match(/^Invalid state/); 63 | done(); 64 | }); 65 | }); 66 | 67 | it('will receive error from `npm install`', function(done) { 68 | var spawn = sinon.stub().returns({ 69 | stdout: { 70 | on: sinon.stub() 71 | }, 72 | stderr: { 73 | on: sinon.stub() 74 | }, 75 | on: sinon.stub().callsArgWith(1, 1) 76 | }); 77 | var addtest = proxyquire('../../lib/scaffold/add', { 78 | 'child_process': { 79 | spawn: spawn 80 | } 81 | }); 82 | 83 | addtest({ 84 | path: path.resolve(testDir, 's0/s1/'), 85 | services: ['a', 'b', 'c'] 86 | }, function(err) { 87 | should.exist(err); 88 | err.message.should.equal('There was an error installing service: a'); 89 | done(); 90 | }); 91 | }); 92 | 93 | it('will update qtumcore-node.json services', function(done) { 94 | var callCount = 0; 95 | var oldPackage = { 96 | dependencies: { 97 | 'qtumcore-lib': '^v0.13.7', 98 | 'qtumcore-node': '^v0.2.0' 99 | } 100 | }; 101 | var spawn = sinon.stub().returns({ 102 | stdout: { 103 | on: sinon.stub() 104 | }, 105 | stderr: { 106 | on: sinon.stub() 107 | }, 108 | on: sinon.stub().callsArgWith(1, 0) 109 | }); 110 | var addtest = proxyquire('../../lib/scaffold/add', { 111 | 'child_process': { 112 | spawn: spawn 113 | }, 114 | 'fs': { 115 | readFileSync: function() { 116 | if (callCount === 1){ 117 | oldPackage.dependencies.a = '^v0.1'; 118 | } else if (callCount === 2){ 119 | oldPackage.dependencies.b = '^v0.1'; 120 | } else if (callCount === 3){ 121 | oldPackage.dependencies.c = '^v0.1'; 122 | } 123 | callCount++; 124 | return JSON.stringify(oldPackage); 125 | } 126 | } 127 | }); 128 | addtest({ 129 | path: path.resolve(testDir, 's0/s1/'), 130 | services: ['a', 'b', 'c'] 131 | }, function(err) { 132 | should.not.exist(err); 133 | var configPath = path.resolve(testDir, 's0/s1/qtumcore-node.json'); 134 | var config = JSON.parse(fs.readFileSync(configPath)); 135 | config.services.should.deep.equal(['a','b','c']); 136 | done(); 137 | }); 138 | }); 139 | 140 | }); 141 | 142 | }); 143 | -------------------------------------------------------------------------------- /test/scaffold/call-method.unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | var sinon = require('sinon'); 5 | var proxyquire = require('proxyquire'); 6 | var EventEmitter = require('events').EventEmitter; 7 | 8 | describe('#callMethod', function() { 9 | 10 | var expectedUrl = 'http://localhost:3001'; 11 | var expectedOptions = { 12 | reconnection: false, 13 | connect_timeout: 5000 14 | }; 15 | 16 | var callOptions = { 17 | host: 'localhost', 18 | port: 3001, 19 | protocol: 'http' 20 | }; 21 | 22 | var callMethod; 23 | 24 | before(function() { 25 | callMethod = proxyquire('../../lib/scaffold/call-method', { 26 | 'socket.io-client': function(url, options) { 27 | url.should.equal(expectedUrl); 28 | options.should.deep.equal(expectedOptions); 29 | return new EventEmitter(); 30 | } 31 | }); 32 | }); 33 | 34 | it('handle a connection error', function(done) { 35 | var socket = callMethod(callOptions, 'getInfo', null, function(err) { 36 | should.exist(err); 37 | err.message.should.equal('connect'); 38 | done(); 39 | }); 40 | socket.emit('connect_error', new Error('connect')); 41 | }); 42 | 43 | it('give an error response', function(done) { 44 | var socket = callMethod(callOptions, 'getInfo', null, function(err) { 45 | should.exist(err); 46 | err.message.should.equal('response'); 47 | done(); 48 | }); 49 | socket.send = function(opts, callback) { 50 | opts.method.should.equal('getInfo'); 51 | should.equal(opts.params, null); 52 | var response = { 53 | error: { 54 | message: 'response' 55 | } 56 | }; 57 | callback(response); 58 | }; 59 | socket.emit('connect'); 60 | }); 61 | 62 | it('give result and close socket', function(done) { 63 | var expectedData = { 64 | version: 110000, 65 | protocolversion: 70002, 66 | blocks: 258614, 67 | timeoffset: -2, 68 | connections: 8, 69 | difficulty: 112628548.66634709, 70 | testnet: false, 71 | relayfee: 1000, 72 | errors: '' 73 | }; 74 | var socket = callMethod(callOptions, 'getInfo', null, function(err, data) { 75 | should.not.exist(err); 76 | data.should.deep.equal(expectedData); 77 | socket.close.callCount.should.equal(1); 78 | done(); 79 | }); 80 | socket.close = sinon.stub(); 81 | socket.send = function(opts, callback) { 82 | opts.method.should.equal('getInfo'); 83 | should.equal(opts.params, null); 84 | var response = { 85 | error: null, 86 | result: expectedData 87 | }; 88 | callback(response); 89 | }; 90 | socket.emit('connect'); 91 | }); 92 | 93 | }); 94 | -------------------------------------------------------------------------------- /test/scaffold/create.integration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | var proxyquire = require('proxyquire'); 5 | var sinon = require('sinon'); 6 | var create = proxyquire('../../lib/scaffold/create', { 7 | 'child_process': { 8 | spawn: sinon.stub().returns({ 9 | stdout: { 10 | on: sinon.stub() 11 | }, 12 | stderr: { 13 | on: sinon.stub() 14 | }, 15 | on: function(event, cb) { 16 | cb(0); 17 | } 18 | }) 19 | } 20 | }); 21 | var fs = require('fs'); 22 | var mkdirp = require('mkdirp'); 23 | var rimraf = require('rimraf'); 24 | 25 | describe('#create', function() { 26 | 27 | var basePath = __dirname + '/../'; 28 | var testDir = basePath + 'temporary-test-data'; 29 | 30 | before(function(done) { 31 | // setup testing directories 32 | mkdirp(testDir, function(err) { 33 | if (err) { 34 | throw err; 35 | } 36 | mkdirp(testDir + '/.qtum', function(err) { 37 | if (err) { 38 | throw err; 39 | } 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | after(function(done) { 46 | // cleanup testing directories 47 | rimraf(testDir, function(err) { 48 | if (err) { 49 | throw err; 50 | } 51 | done(); 52 | }); 53 | }); 54 | 55 | it('will create scaffold files', function() { 56 | create({ 57 | cwd: testDir, 58 | dirname: 'mynode', 59 | name: 'My Node 1', 60 | isGlobal: false, 61 | datadir: './data' 62 | }, function(err) { 63 | if (err) { 64 | throw err; 65 | } 66 | 67 | var configPath = testDir + '/mynode/qtumcore-node.json'; 68 | var packagePath = testDir + '/mynode/package.json'; 69 | 70 | should.equal(fs.existsSync(configPath), true); 71 | should.equal(fs.existsSync(packagePath), true); 72 | 73 | var config = JSON.parse(fs.readFileSync(configPath)); 74 | config.services.should.deep.equal(['qtumd', 'db', 'address', 'web']); 75 | config.datadir.should.equal('./data'); 76 | config.network.should.equal('livenet'); 77 | 78 | var pack = JSON.parse(fs.readFileSync(packagePath)); 79 | should.exist(pack.dependencies); 80 | 81 | }); 82 | 83 | }); 84 | 85 | it('will error if directory already exists', function() { 86 | 87 | create({ 88 | cwd: testDir, 89 | dirname: 'mynode', 90 | name: 'My Node 2', 91 | isGlobal: false, 92 | datadir: './data' 93 | }, function(err) { 94 | should.exist(err); 95 | err.message.should.match(/^Directory/); 96 | }); 97 | 98 | }); 99 | 100 | it('will not create a package.json if globally installed', function() { 101 | 102 | create({ 103 | cwd: testDir, 104 | dirname: 'mynode3', 105 | name: 'My Node 3', 106 | isGlobal: true, 107 | datadir: '../.qtum' 108 | }, function(err) { 109 | if (err) { 110 | throw err; 111 | } 112 | 113 | var packagePath = testDir + '/mynode3/package.json'; 114 | should.equal(fs.existsSync(packagePath), false); 115 | 116 | }); 117 | 118 | }); 119 | 120 | it('will receieve an error from npm', function() { 121 | var createtest = proxyquire('../../lib/scaffold/create', { 122 | 'child_process': { 123 | spawn: sinon.stub().returns({ 124 | stdout: { 125 | on: sinon.stub() 126 | }, 127 | stderr: { 128 | on: sinon.stub() 129 | }, 130 | on: function(event, cb) { 131 | cb(1); 132 | } 133 | }) 134 | } 135 | }); 136 | 137 | createtest({ 138 | cwd: testDir, 139 | dirname: 'mynode4', 140 | name: 'My Node 4', 141 | isGlobal: false, 142 | datadir: '../.qtum' 143 | }, function(err) { 144 | should.exist(err); 145 | err.message.should.equal('There was an error installing dependencies.'); 146 | }); 147 | 148 | }); 149 | 150 | }); 151 | -------------------------------------------------------------------------------- /test/scaffold/default-base-config.integration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | var path = require('path'); 5 | var defaultBaseConfig = require('../../lib/scaffold/default-base-config'); 6 | 7 | describe('#defaultBaseConfig', function() { 8 | it('will return expected configuration', function() { 9 | var cwd = process.cwd(); 10 | var home = process.env.HOME; 11 | var info = defaultBaseConfig(); 12 | info.path.should.equal(cwd); 13 | info.config.network.should.equal('livenet'); 14 | info.config.port.should.equal(3001); 15 | info.config.services.should.deep.equal(['qtumd', 'web']); 16 | var qtumd = info.config.servicesConfig.qtumd; 17 | qtumd.spawn.datadir.should.equal(home + '/.qtum'); 18 | qtumd.spawn.exec.should.equal(path.resolve(__dirname, '../../bin/qtumd')); 19 | }); 20 | it('be able to specify a network', function() { 21 | var info = defaultBaseConfig({network: 'testnet'}); 22 | info.config.network.should.equal('testnet'); 23 | }); 24 | it('be able to specify a datadir', function() { 25 | var info = defaultBaseConfig({datadir: './data2', network: 'testnet'}); 26 | info.config.servicesConfig.qtumd.spawn.datadir.should.equal('./data2'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/scaffold/default-config.integration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var should = require('chai').should(); 5 | var sinon = require('sinon'); 6 | var proxyquire = require('proxyquire'); 7 | 8 | describe('#defaultConfig', function() { 9 | var expectedExecPath = path.resolve(__dirname, '../../bin/qtumd'); 10 | 11 | it('will return expected configuration', function() { 12 | var config = JSON.stringify({ 13 | network: 'livenet', 14 | port: 3001, 15 | services: [ 16 | 'qtumd', 17 | 'web' 18 | ], 19 | servicesConfig: { 20 | qtumd: { 21 | spawn: { 22 | datadir: process.env.HOME + '/.bitcore/data', 23 | exec: expectedExecPath 24 | } 25 | } 26 | } 27 | }, null, 2); 28 | var defaultConfig = proxyquire('../../lib/scaffold/default-config', { 29 | fs: { 30 | existsSync: sinon.stub().returns(false), 31 | writeFileSync: function(path, data) { 32 | path.should.equal(process.env.HOME + '/.bitcore/qtumcore-node.json'); 33 | data.should.equal(config); 34 | }, 35 | readFileSync: function() { 36 | return config; 37 | } 38 | }, 39 | mkdirp: { 40 | sync: sinon.stub() 41 | } 42 | }); 43 | var home = process.env.HOME; 44 | var info = defaultConfig(); 45 | info.path.should.equal(home + '/.bitcore'); 46 | info.config.network.should.equal('livenet'); 47 | info.config.port.should.equal(3001); 48 | info.config.services.should.deep.equal(['qtumd', 'web']); 49 | var qtumd = info.config.servicesConfig.qtumd; 50 | should.exist(qtumd); 51 | qtumd.spawn.datadir.should.equal(home + '/.bitcore/data'); 52 | qtumd.spawn.exec.should.equal(expectedExecPath); 53 | }); 54 | it('will include additional services', function() { 55 | var config = JSON.stringify({ 56 | network: 'livenet', 57 | port: 3001, 58 | services: [ 59 | 'qtumd', 60 | 'web', 61 | 'insight-api', 62 | 'insight-ui' 63 | ], 64 | servicesConfig: { 65 | qtumd: { 66 | spawn: { 67 | datadir: process.env.HOME + '/.bitcore/data', 68 | exec: expectedExecPath 69 | } 70 | } 71 | } 72 | }, null, 2); 73 | var defaultConfig = proxyquire('../../lib/scaffold/default-config', { 74 | fs: { 75 | existsSync: sinon.stub().returns(false), 76 | writeFileSync: function(path, data) { 77 | path.should.equal(process.env.HOME + '/.bitcore/qtumcore-node.json'); 78 | data.should.equal(config); 79 | }, 80 | readFileSync: function() { 81 | return config; 82 | } 83 | }, 84 | mkdirp: { 85 | sync: sinon.stub() 86 | } 87 | }); 88 | var home = process.env.HOME; 89 | var info = defaultConfig({ 90 | additionalServices: ['insight-api', 'insight-ui'] 91 | }); 92 | info.path.should.equal(home + '/.bitcore'); 93 | info.config.network.should.equal('livenet'); 94 | info.config.port.should.equal(3001); 95 | info.config.services.should.deep.equal([ 96 | 'qtumd', 97 | 'web', 98 | 'insight-api', 99 | 'insight-ui' 100 | ]); 101 | var qtumd = info.config.servicesConfig.qtumd; 102 | should.exist(qtumd); 103 | qtumd.spawn.datadir.should.equal(home + '/.bitcore/data'); 104 | qtumd.spawn.exec.should.equal(expectedExecPath); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /test/scaffold/find-config.integration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var should = require('chai').should(); 6 | var sinon = require('sinon'); 7 | var mkdirp = require('mkdirp'); 8 | var rimraf = require('rimraf'); 9 | 10 | var findConfig = require('../../lib/scaffold/find-config'); 11 | 12 | describe('#findConfig', function() { 13 | 14 | var testDir = path.resolve(__dirname, '../temporary-test-data'); 15 | var expectedConfig = { 16 | name: 'My Node' 17 | }; 18 | 19 | before(function(done) { 20 | // setup testing directories 21 | mkdirp(testDir + '/p2/p1/p0', function(err) { 22 | if (err) { 23 | throw err; 24 | } 25 | fs.writeFile( 26 | testDir + '/p2/qtumcore-node.json', 27 | JSON.stringify(expectedConfig), 28 | function() { 29 | mkdirp(testDir + '/e0', function(err) { 30 | if (err) { 31 | throw err; 32 | } 33 | done(); 34 | }); 35 | } 36 | ); 37 | }); 38 | 39 | }); 40 | 41 | after(function(done) { 42 | // cleanup testing directories 43 | rimraf(testDir, function(err) { 44 | if (err) { 45 | throw err; 46 | } 47 | done(); 48 | }); 49 | }); 50 | 51 | 52 | describe('will find a configuration file', function() { 53 | 54 | it('in the current directory', function() { 55 | var config = findConfig(path.resolve(testDir, 'p2')); 56 | config.path.should.equal(path.resolve(testDir, 'p2')); 57 | config.config.should.deep.equal(expectedConfig); 58 | }); 59 | 60 | it('in a parent directory', function() { 61 | var config = findConfig(path.resolve(testDir, 'p2/p1')); 62 | config.path.should.equal(path.resolve(testDir, 'p2')); 63 | config.config.should.deep.equal(expectedConfig); 64 | }); 65 | 66 | it('recursively find in parent directories', function() { 67 | var config = findConfig(path.resolve(testDir, 'p2/p1/p0')); 68 | config.path.should.equal(path.resolve(testDir, 'p2')); 69 | config.config.should.deep.equal(expectedConfig); 70 | }); 71 | 72 | }); 73 | 74 | it('will return false if missing a configuration', function() { 75 | var config = findConfig(path.resolve(testDir, 'e0')); 76 | config.should.equal(false); 77 | }); 78 | 79 | }); 80 | -------------------------------------------------------------------------------- /test/scaffold/remove.integration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | var sinon = require('sinon'); 5 | var path = require('path'); 6 | var fs = require('fs'); 7 | var proxyquire = require('proxyquire'); 8 | var mkdirp = require('mkdirp'); 9 | var rimraf = require('rimraf'); 10 | var remove = require('../../lib/scaffold/remove'); 11 | 12 | describe('#remove', function() { 13 | 14 | var basePath = path.resolve(__dirname, '..'); 15 | var testDir = path.resolve(basePath, 'temporary-test-data'); 16 | var startConfig = { 17 | name: 'My Node', 18 | services: ['a', 'b', 'c'] 19 | }; 20 | var startPackage = {}; 21 | 22 | before(function(done) { 23 | mkdirp(testDir + '/s0/s1', function(err) { 24 | if (err) { 25 | throw err; 26 | } 27 | fs.writeFile( 28 | testDir + '/s0/s1/qtumcore-node.json', 29 | JSON.stringify(startConfig), 30 | function(err) { 31 | if (err) { 32 | throw err; 33 | } 34 | fs.writeFile( 35 | testDir + '/s0/s1/package.json', 36 | JSON.stringify(startPackage), 37 | done 38 | ); 39 | } 40 | ); 41 | }); 42 | }); 43 | 44 | after(function(done) { 45 | // cleanup testing directories 46 | rimraf(testDir, function(err) { 47 | if (err) { 48 | throw err; 49 | } 50 | done(); 51 | }); 52 | }); 53 | 54 | describe('will modify scaffold files', function() { 55 | 56 | it('will give an error if expected files do not exist', function(done) { 57 | remove({ 58 | path: path.resolve(testDir, 's0'), 59 | services: ['b'] 60 | }, function(err) { 61 | should.exist(err); 62 | err.message.match(/^Invalid state/); 63 | done(); 64 | }); 65 | }); 66 | 67 | it('will update qtumcore-node.json services', function(done) { 68 | var spawn = sinon.stub().returns({ 69 | stdout: { 70 | on: sinon.stub() 71 | }, 72 | stderr: { 73 | on: sinon.stub() 74 | }, 75 | on: sinon.stub().callsArgWith(1, 0) 76 | }); 77 | var removetest = proxyquire('../../lib/scaffold/remove', { 78 | 'child_process': { 79 | spawn: spawn 80 | }, 81 | 'npm': { 82 | load: sinon.stub().callsArg(0), 83 | commands: { 84 | ls: sinon.stub().callsArgWith(2, null, {}, { 85 | dependencies: {} 86 | }) 87 | } 88 | } 89 | }); 90 | removetest({ 91 | path: path.resolve(testDir, 's0/s1/'), 92 | services: ['b'] 93 | }, function(err) { 94 | should.not.exist(err); 95 | var configPath = path.resolve(testDir, 's0/s1/qtumcore-node.json'); 96 | var config = JSON.parse(fs.readFileSync(configPath)); 97 | config.services.should.deep.equal(['a', 'c']); 98 | done(); 99 | }); 100 | }); 101 | 102 | it('will receive error from `npm uninstall`', function(done) { 103 | var spawn = sinon.stub().returns({ 104 | stdout: { 105 | on: sinon.stub() 106 | }, 107 | stderr: { 108 | on: sinon.stub() 109 | }, 110 | on: sinon.stub().callsArgWith(1, 1) 111 | }); 112 | var removetest = proxyquire('../../lib/scaffold/remove', { 113 | 'child_process': { 114 | spawn: spawn 115 | }, 116 | 'npm': { 117 | load: sinon.stub().callsArg(0), 118 | commands: { 119 | ls: sinon.stub().callsArgWith(2, null, {}, { 120 | dependencies: {} 121 | }) 122 | } 123 | } 124 | }); 125 | 126 | removetest({ 127 | path: path.resolve(testDir, 's0/s1/'), 128 | services: ['b'] 129 | }, function(err) { 130 | should.exist(err); 131 | err.message.should.equal('There was an error uninstalling service(s): b'); 132 | done(); 133 | }); 134 | }); 135 | 136 | }); 137 | 138 | }); 139 | -------------------------------------------------------------------------------- /test/scaffold/start.integration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | var sinon = require('sinon'); 5 | var proxyquire = require('proxyquire'); 6 | var QtumService = require('../../lib/services/qtumd'); 7 | var index = require('../../lib'); 8 | var log = index.log; 9 | 10 | describe('#start', function() { 11 | 12 | var sandbox = sinon.sandbox.create(); 13 | beforeEach(function() { 14 | sandbox.stub(log, 'error'); 15 | }); 16 | afterEach(function() { 17 | sandbox.restore(); 18 | }); 19 | 20 | describe('will dynamically create a node from a configuration', function() { 21 | 22 | it('require each qtumcore-node service with default config', function(done) { 23 | var node; 24 | var TestNode = function(options) { 25 | options.services[0].should.deep.equal({ 26 | name: 'qtumd', 27 | module: QtumService, 28 | config: { 29 | spawn: { 30 | datadir: './data' 31 | } 32 | } 33 | }); 34 | }; 35 | TestNode.prototype.start = sinon.stub().callsArg(0); 36 | TestNode.prototype.on = sinon.stub(); 37 | TestNode.prototype.chain = { 38 | on: sinon.stub() 39 | }; 40 | 41 | var starttest = proxyquire('../../lib/scaffold/start', { 42 | '../node': TestNode 43 | }); 44 | 45 | starttest.registerExitHandlers = sinon.stub(); 46 | 47 | node = starttest({ 48 | path: __dirname, 49 | config: { 50 | services: [ 51 | 'qtumd' 52 | ], 53 | servicesConfig: { 54 | qtumd: { 55 | spawn: { 56 | datadir: './data' 57 | } 58 | } 59 | } 60 | } 61 | }); 62 | node.should.be.instanceof(TestNode); 63 | done(); 64 | }); 65 | it('shutdown with an error from start', function(done) { 66 | var TestNode = proxyquire('../../lib/node', {}); 67 | TestNode.prototype.start = function(callback) { 68 | setImmediate(function() { 69 | callback(new Error('error')); 70 | }); 71 | }; 72 | var starttest = proxyquire('../../lib/scaffold/start', { 73 | '../node': TestNode 74 | }); 75 | starttest.cleanShutdown = sinon.stub(); 76 | starttest.registerExitHandlers = sinon.stub(); 77 | 78 | starttest({ 79 | path: __dirname, 80 | config: { 81 | services: [], 82 | servicesConfig: {} 83 | } 84 | }); 85 | setImmediate(function() { 86 | starttest.cleanShutdown.callCount.should.equal(1); 87 | done(); 88 | }); 89 | }); 90 | it('require each qtumcore-node service with explicit config', function(done) { 91 | var node; 92 | var TestNode = function(options) { 93 | options.services[0].should.deep.equal({ 94 | name: 'qtumd', 95 | module: QtumService, 96 | config: { 97 | param: 'test', 98 | spawn: { 99 | datadir: './data' 100 | } 101 | } 102 | }); 103 | }; 104 | TestNode.prototype.start = sinon.stub().callsArg(0); 105 | TestNode.prototype.on = sinon.stub(); 106 | TestNode.prototype.chain = { 107 | on: sinon.stub() 108 | }; 109 | 110 | var starttest = proxyquire('../../lib/scaffold/start', { 111 | '../node': TestNode 112 | }); 113 | starttest.registerExitHandlers = sinon.stub(); 114 | 115 | node = starttest({ 116 | path: __dirname, 117 | config: { 118 | services: [ 119 | 'qtumd' 120 | ], 121 | servicesConfig: { 122 | 'qtumd': { 123 | param: 'test', 124 | spawn: { 125 | datadir: './data' 126 | } 127 | } 128 | }, 129 | 130 | } 131 | }); 132 | node.should.be.instanceof(TestNode); 133 | done(); 134 | }); 135 | }); 136 | }); 137 | -------------------------------------------------------------------------------- /test/scaffold/start.unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | var EventEmitter = require('events').EventEmitter; 5 | var path = require('path'); 6 | var sinon = require('sinon'); 7 | var proxyquire = require('proxyquire'); 8 | var start = require('../../lib/scaffold/start'); 9 | 10 | describe('#start', function() { 11 | describe('#checkConfigVersion2', function() { 12 | var sandbox = sinon.sandbox.create(); 13 | beforeEach(function() { 14 | sandbox.stub(console, 'warn'); 15 | }); 16 | afterEach(function() { 17 | sandbox.restore(); 18 | }); 19 | it('will give true with "datadir" at root', function() { 20 | var checkConfigVersion2 = proxyquire('../../lib/scaffold/start', {}).checkConfigVersion2; 21 | var v2 = checkConfigVersion2({datadir: '/home/user/.qtumcore/data', services: []}); 22 | v2.should.equal(true); 23 | }); 24 | it('will give true with "address" service enabled', function() { 25 | var checkConfigVersion2 = proxyquire('../../lib/scaffold/start', {}).checkConfigVersion2; 26 | var v2 = checkConfigVersion2({services: ['address']}); 27 | v2.should.equal(true); 28 | }); 29 | it('will give true with "db" service enabled', function() { 30 | var checkConfigVersion2 = proxyquire('../../lib/scaffold/start', {}).checkConfigVersion2; 31 | var v2 = checkConfigVersion2({services: ['db']}); 32 | v2.should.equal(true); 33 | }); 34 | it('will give false without "datadir" at root and "address", "db" services disabled', function() { 35 | var checkConfigVersion2 = proxyquire('../../lib/scaffold/start', {}).checkConfigVersion2; 36 | var v2 = checkConfigVersion2({services: []}); 37 | v2.should.equal(false); 38 | }); 39 | }); 40 | describe('#setupServices', function() { 41 | var cwd = process.cwd(); 42 | var setupServices = proxyquire('../../lib/scaffold/start', {}).setupServices; 43 | it('will require an internal module', function() { 44 | function InternalService() {} 45 | InternalService.dependencies = []; 46 | InternalService.prototype.start = sinon.stub(); 47 | InternalService.prototype.stop = sinon.stub(); 48 | var expectedPath = path.resolve(__dirname, '../../lib/services/internal'); 49 | var testRequire = function(p) { 50 | p.should.equal(expectedPath); 51 | return InternalService; 52 | }; 53 | var config = { 54 | services: ['internal'], 55 | servicesConfig: { 56 | internal: { 57 | param: 'value' 58 | } 59 | } 60 | }; 61 | var services = setupServices(testRequire, cwd, config); 62 | services[0].name.should.equal('internal'); 63 | services[0].config.should.deep.equal({param: 'value'}); 64 | services[0].module.should.equal(InternalService); 65 | }); 66 | it('will require a local module', function() { 67 | function LocalService() {} 68 | LocalService.dependencies = []; 69 | LocalService.prototype.start = sinon.stub(); 70 | LocalService.prototype.stop = sinon.stub(); 71 | var notfoundPath = path.resolve(__dirname, '../../lib/services/local'); 72 | var testRequire = function(p) { 73 | if (p === notfoundPath) { 74 | throw new Error(); 75 | } else if (p === 'local') { 76 | return LocalService; 77 | } else if (p === 'local/package.json') { 78 | return { 79 | name: 'local' 80 | }; 81 | } 82 | }; 83 | var config = { 84 | services: ['local'] 85 | }; 86 | var services = setupServices(testRequire, cwd, config); 87 | services[0].name.should.equal('local'); 88 | services[0].module.should.equal(LocalService); 89 | }); 90 | it('will require a local module with "qtumcoreNode" in package.json', function() { 91 | function LocalService() {} 92 | LocalService.dependencies = []; 93 | LocalService.prototype.start = sinon.stub(); 94 | LocalService.prototype.stop = sinon.stub(); 95 | var notfoundPath = path.resolve(__dirname, '../../lib/services/local'); 96 | var testRequire = function(p) { 97 | if (p === notfoundPath) { 98 | throw new Error(); 99 | } else if (p === 'local/package.json') { 100 | return { 101 | name: 'local', 102 | qtumcoreNode: 'lib/qtumcoreNode.js' 103 | }; 104 | } else if (p === 'local/lib/qtumcoreNode.js') { 105 | return LocalService; 106 | } 107 | }; 108 | var config = { 109 | services: ['local'] 110 | }; 111 | var services = setupServices(testRequire, cwd, config); 112 | services[0].name.should.equal('local'); 113 | services[0].module.should.equal(LocalService); 114 | }); 115 | it('will throw error if module is incompatible', function() { 116 | var internal = {}; 117 | var testRequire = function() { 118 | return internal; 119 | }; 120 | var config = { 121 | services: ['qtumd'] 122 | }; 123 | (function() { 124 | setupServices(testRequire, cwd, config); 125 | }).should.throw('Could not load service'); 126 | }); 127 | }); 128 | describe('#cleanShutdown', function() { 129 | it('will call node stop and process exit', function() { 130 | var log = { 131 | info: sinon.stub(), 132 | error: sinon.stub() 133 | }; 134 | var cleanShutdown = proxyquire('../../lib/scaffold/start', { 135 | '../': { 136 | log: log 137 | } 138 | }).cleanShutdown; 139 | var node = { 140 | stop: sinon.stub().callsArg(0) 141 | }; 142 | var _process = { 143 | exit: sinon.stub() 144 | }; 145 | cleanShutdown(_process, node); 146 | setImmediate(function() { 147 | node.stop.callCount.should.equal(1); 148 | _process.exit.callCount.should.equal(1); 149 | _process.exit.args[0][0].should.equal(0); 150 | }); 151 | }); 152 | it('will log error during shutdown and exit with status 1', function() { 153 | var log = { 154 | info: sinon.stub(), 155 | error: sinon.stub() 156 | }; 157 | var cleanShutdown = proxyquire('../../lib/scaffold/start', { 158 | '../': { 159 | log: log 160 | } 161 | }).cleanShutdown; 162 | var node = { 163 | stop: sinon.stub().callsArgWith(0, new Error('test')) 164 | }; 165 | var _process = { 166 | exit: sinon.stub() 167 | }; 168 | cleanShutdown(_process, node); 169 | setImmediate(function() { 170 | node.stop.callCount.should.equal(1); 171 | log.error.callCount.should.equal(1); 172 | _process.exit.callCount.should.equal(1); 173 | _process.exit.args[0][0].should.equal(1); 174 | }); 175 | }); 176 | }); 177 | describe('#registerExitHandlers', function() { 178 | var log = { 179 | info: sinon.stub(), 180 | error: sinon.stub() 181 | }; 182 | var registerExitHandlers = proxyquire('../../lib/scaffold/start', { 183 | '../': { 184 | log: log 185 | } 186 | }).registerExitHandlers; 187 | it('log, stop and exit with an `uncaughtException`', function(done) { 188 | var proc = new EventEmitter(); 189 | proc.exit = sinon.stub(); 190 | var node = { 191 | stop: sinon.stub().callsArg(0) 192 | }; 193 | registerExitHandlers(proc, node); 194 | proc.emit('uncaughtException', new Error('test')); 195 | setImmediate(function() { 196 | node.stop.callCount.should.equal(1); 197 | proc.exit.callCount.should.equal(1); 198 | done(); 199 | }); 200 | }); 201 | it('stop and exit on `SIGINT`', function(done) { 202 | var proc = new EventEmitter(); 203 | proc.exit = sinon.stub(); 204 | var node = { 205 | stop: sinon.stub().callsArg(0) 206 | }; 207 | registerExitHandlers(proc, node); 208 | proc.emit('SIGINT'); 209 | setImmediate(function() { 210 | node.stop.callCount.should.equal(1); 211 | proc.exit.callCount.should.equal(1); 212 | done(); 213 | }); 214 | }); 215 | }); 216 | describe('#registerExitHandlers', function() { 217 | var stub; 218 | var registerExitHandlers = require('../../lib/scaffold/start').registerExitHandlers; 219 | 220 | before(function() { 221 | stub = sinon.stub(process, 'on'); 222 | }); 223 | 224 | after(function() { 225 | stub.restore(); 226 | }); 227 | 228 | it('should setup two listeners on process when registering exit handlers', function() { 229 | registerExitHandlers(process, {}); 230 | stub.callCount.should.equal(2); 231 | }); 232 | 233 | describe('#exitHandler', function() { 234 | var sandbox; 235 | var cleanShutdown; 236 | var exitHandler; 237 | var logStub; 238 | 239 | before(function() { 240 | sandbox = sinon.sandbox.create(); 241 | var start = require('../../lib/scaffold/start'); 242 | var log = require('../../lib').log; 243 | logStub = sandbox.stub(log, 'error'); 244 | cleanShutdown = sandbox.stub(start, 'cleanShutdown', function() {}); 245 | exitHandler = require('../../lib/scaffold/start').exitHandler; 246 | }); 247 | 248 | after(function() { 249 | sandbox.restore(); 250 | }); 251 | 252 | it('should replace the listener for SIGINT after the first SIGINT is handled', function() { 253 | var options = { sigint: true }; 254 | var node = {}; 255 | exitHandler(options, process, node); 256 | cleanShutdown.callCount.should.equal(1); 257 | exitHandler(options, process, node); 258 | cleanShutdown.callCount.should.equal(1); 259 | }); 260 | 261 | it('should log all errors and stops the services nonetheless', function() { 262 | var options = { sigint: true }; 263 | var stop = sinon.stub(); 264 | var node = { 265 | stop: stop 266 | }; 267 | exitHandler(options, process, node, new Error('some error')); 268 | logStub.callCount.should.equal(2); 269 | stop.callCount.should.equal(1); 270 | }); 271 | 272 | }); 273 | }); 274 | }); 275 | -------------------------------------------------------------------------------- /test/services/web.unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | var sinon = require('sinon'); 5 | var EventEmitter = require('events').EventEmitter; 6 | var proxyquire = require('proxyquire'); 7 | 8 | var index = require('../../lib'); 9 | var log = index.log; 10 | 11 | var httpStub = { 12 | createServer: sinon.spy() 13 | }; 14 | var httpsStub = { 15 | createServer: sinon.spy() 16 | }; 17 | var fsStub = { 18 | readFileSync: function(arg1) { 19 | return arg1 + '-buffer'; 20 | } 21 | }; 22 | 23 | var fakeSocketListener = new EventEmitter(); 24 | var fakeSocket = new EventEmitter(); 25 | 26 | fakeSocket.on('test/event1', function(data) { 27 | data.should.equal('testdata'); 28 | }); 29 | 30 | fakeSocketListener.emit('connection', fakeSocket); 31 | fakeSocket.emit('subscribe', 'test/event1'); 32 | 33 | var WebService = proxyquire('../../lib/services/web', {http: httpStub, https: httpsStub, fs: fsStub}); 34 | 35 | describe('WebService', function() { 36 | var defaultNode = new EventEmitter(); 37 | 38 | describe('@constructor', function() { 39 | it('will set socket rpc settings', function() { 40 | var web = new WebService({node: defaultNode, enableSocketRPC: false}); 41 | web.enableSocketRPC.should.equal(false); 42 | 43 | var web2 = new WebService({node: defaultNode, enableSocketRPC: true}); 44 | web2.enableSocketRPC.should.equal(true); 45 | 46 | var web3 = new WebService({node: defaultNode}); 47 | web3.enableSocketRPC.should.equal(WebService.DEFAULT_SOCKET_RPC); 48 | }); 49 | it('will set configuration options for max payload', function() { 50 | var web = new WebService({node: defaultNode, jsonRequestLimit: '200kb'}); 51 | web.jsonRequestLimit.should.equal('200kb'); 52 | web.port.should.equal(3456); 53 | }); 54 | it('will set port', function() { 55 | var web = new WebService({node: defaultNode, jsonRequestLimit: '200kb', port: 3000}); 56 | web.jsonRequestLimit.should.equal('200kb'); 57 | web.port.should.equal(3000); 58 | }); 59 | }); 60 | 61 | describe('#start', function() { 62 | beforeEach(function() { 63 | httpStub.createServer.reset(); 64 | httpsStub.createServer.reset(); 65 | }); 66 | it('should create an http server if no options are specified and node is not configured for https', function(done) { 67 | var web = new WebService({node: defaultNode}); 68 | web.deriveHttpsOptions = sinon.spy(); 69 | web.start(function(err) { 70 | should.not.exist(err); 71 | httpStub.createServer.called.should.equal(true); 72 | done(); 73 | }); 74 | }); 75 | 76 | it('should create an https server if no options are specified and node is configured for https', function(done) { 77 | var node = new EventEmitter(); 78 | node.https = true; 79 | 80 | var web = new WebService({node: node}); 81 | web.transformHttpsOptions = sinon.spy(); 82 | web.start(function(err) { 83 | should.not.exist(err); 84 | web.transformHttpsOptions.callCount.should.equal(1); 85 | httpsStub.createServer.called.should.equal(true); 86 | done(); 87 | }); 88 | }); 89 | it('should pass json request limit to json body parser', function(done) { 90 | var node = new EventEmitter(); 91 | var jsonStub = sinon.stub(); 92 | var TestWebService = proxyquire('../../lib/services/web', { 93 | http: { 94 | createServer: sinon.stub() 95 | }, 96 | https: { 97 | createServer: sinon.stub() 98 | }, 99 | fs: fsStub, 100 | express: sinon.stub().returns({ 101 | use: sinon.stub() 102 | }), 103 | 'body-parser': { 104 | json: jsonStub 105 | }, 106 | 'socket.io': { 107 | listen: sinon.stub().returns({ 108 | on: sinon.stub() 109 | }) 110 | } 111 | }); 112 | var web = new TestWebService({node: node}); 113 | sinon.spy(web, 'transformHttpsOptions'); 114 | web.start(function(err) { 115 | if (err) { 116 | return done(err); 117 | } 118 | // no https 119 | web.transformHttpsOptions.callCount.should.equal(0); 120 | jsonStub.callCount.should.equal(1); 121 | jsonStub.args[0][0].limit.should.equal('100kb'); 122 | done(); 123 | }); 124 | }); 125 | }); 126 | 127 | describe('#stop', function() { 128 | it('should close the server if it exists', function(done) { 129 | var web = new WebService({node: defaultNode}); 130 | web.server = { 131 | close: sinon.spy() 132 | }; 133 | 134 | web.stop(function(err) { 135 | should.not.exist(err); 136 | web.server.close.callCount.should.equal(1); 137 | done(); 138 | }); 139 | }); 140 | }); 141 | 142 | describe('#setupAllRoutes', function() { 143 | it('should call setupRoutes on each module', function() { 144 | var node = { 145 | on: sinon.spy(), 146 | services: { 147 | one: { 148 | setupRoutes: sinon.spy(), 149 | getRoutePrefix: sinon.stub().returns('one') 150 | }, 151 | two: { 152 | setupRoutes: sinon.spy(), 153 | getRoutePrefix: sinon.stub().returns('two') 154 | } 155 | } 156 | }; 157 | 158 | var web = new WebService({node: node}); 159 | web.app = { 160 | use: sinon.spy() 161 | }; 162 | 163 | web.setupAllRoutes(); 164 | node.services.one.setupRoutes.callCount.should.equal(1); 165 | should.exist(node.services.one.setupRoutes.args[0][0].engine); 166 | should.exist(node.services.one.setupRoutes.args[0][0].get); 167 | should.exist(node.services.one.setupRoutes.args[0][0].post); 168 | should.exist(node.services.one.setupRoutes.args[0][0].set); 169 | node.services.two.setupRoutes.callCount.should.equal(1); 170 | }); 171 | }); 172 | 173 | describe('#createMethodsMap', function() { 174 | it('should create the methodsMap correctly', function(done) { 175 | var Module1 = function() {}; 176 | Module1.prototype.getAPIMethods = function() { 177 | return [ 178 | ['one', this, this.one, 1], 179 | ['two', this, this.two, 2] 180 | ]; 181 | }; 182 | Module1.prototype.one = function(param1, callback) { 183 | callback(null, param1); 184 | }; 185 | Module1.prototype.two = function(param1, param2, callback) { 186 | callback(null, param1 + param2); 187 | }; 188 | 189 | var module1 = new Module1(); 190 | var getAllAPIMethods = sinon.stub().returns(module1.getAPIMethods()); 191 | var node = { 192 | on: sinon.spy(), 193 | getAllAPIMethods: getAllAPIMethods 194 | }; 195 | 196 | var web = new WebService({node: node}); 197 | web.createMethodsMap(); 198 | getAllAPIMethods.callCount.should.equal(1); 199 | Object.keys(web.methodsMap).length.should.equal(2); 200 | web.methodsMap.one.args.should.equal(1); 201 | web.methodsMap.two.args.should.equal(2); 202 | web.methodsMap.one.fn(1, function(err, result) { 203 | should.not.exist(err); 204 | result.should.equal(1); 205 | 206 | web.methodsMap.two.fn(1, 2, function(err, result) { 207 | should.not.exist(err); 208 | result.should.equal(3); 209 | done(); 210 | }); 211 | }); 212 | }); 213 | }); 214 | 215 | describe('#getEventNames', function() { 216 | it('should get event names', function() { 217 | var Module1 = function() {}; 218 | Module1.prototype.getPublishEvents = function() { 219 | return [ 220 | { 221 | name: 'event1', 222 | extraEvents: ['event2'] 223 | } 224 | ]; 225 | }; 226 | 227 | var module1 = new Module1(); 228 | var getAllPublishEvents = sinon.stub().returns(module1.getPublishEvents()); 229 | var node = { 230 | on: sinon.spy(), 231 | getAllPublishEvents: getAllPublishEvents 232 | }; 233 | 234 | var web = new WebService({node: node}); 235 | var events = web.getEventNames(); 236 | getAllPublishEvents.callCount.should.equal(1); 237 | events.should.deep.equal(['event1', 'event2']); 238 | }); 239 | 240 | it('should throw an error if there is a duplicate event', function() { 241 | var Module1 = function() {}; 242 | Module1.prototype.getPublishEvents = function() { 243 | return [ 244 | { 245 | name: 'event1', 246 | extraEvents: ['event1'] 247 | } 248 | ]; 249 | }; 250 | 251 | var module1 = new Module1(); 252 | var node = { 253 | on: sinon.spy(), 254 | getAllPublishEvents: sinon.stub().returns(module1.getPublishEvents()) 255 | }; 256 | 257 | var web = new WebService({node: node}); 258 | (function() { 259 | var events = web.getEventNames(); 260 | }).should.throw('Duplicate event event1'); 261 | }); 262 | }); 263 | 264 | describe('#_getRemoteAddress', function() { 265 | it('will get remote address from cloudflare header', function() { 266 | var web = new WebService({node: defaultNode}); 267 | var socket = {}; 268 | socket.conn = {}; 269 | socket.client = {}; 270 | socket.client.request = {}; 271 | socket.client.request.headers = { 272 | 'cf-connecting-ip': '127.0.0.1' 273 | }; 274 | var remoteAddress = web._getRemoteAddress(socket); 275 | remoteAddress.should.equal('127.0.0.1'); 276 | }); 277 | it('will get remote address from connection', function() { 278 | var web = new WebService({node: defaultNode}); 279 | var socket = {}; 280 | socket.conn = {}; 281 | socket.conn.remoteAddress = '127.0.0.1'; 282 | socket.client = {}; 283 | socket.client.request = {}; 284 | socket.client.request.headers = {}; 285 | var remoteAddress = web._getRemoteAddress(socket); 286 | remoteAddress.should.equal('127.0.0.1'); 287 | }); 288 | }); 289 | 290 | describe('#socketHandler', function() { 291 | var sandbox = sinon.sandbox.create(); 292 | beforeEach(function() { 293 | sandbox.stub(log, 'info'); 294 | }); 295 | afterEach(function() { 296 | sandbox.restore(); 297 | }); 298 | 299 | var bus = new EventEmitter(); 300 | bus.remoteAddress = '127.0.0.1'; 301 | 302 | var Module1 = function() {}; 303 | Module1.prototype.getPublishEvents = function() { 304 | return [ 305 | { 306 | name: 'event1', 307 | extraEvents: ['event2'] 308 | } 309 | ]; 310 | }; 311 | 312 | var module1 = new Module1(); 313 | var node = { 314 | on: sinon.spy(), 315 | openBus: sinon.stub().returns(bus), 316 | getAllPublishEvents: sinon.stub().returns(module1.getPublishEvents()) 317 | }; 318 | 319 | var web; 320 | var socket; 321 | 322 | it('on message should call socketMessageHandler', function(done) { 323 | web = new WebService({node: node}); 324 | web.eventNames = web.getEventNames(); 325 | web.socketMessageHandler = function(param1) { 326 | param1.should.equal('data'); 327 | done(); 328 | }; 329 | socket = new EventEmitter(); 330 | socket.conn = {}; 331 | socket.conn.remoteAddress = '127.0.0.1'; 332 | socket.client = {}; 333 | socket.client.request = {}; 334 | socket.client.request.headers = {}; 335 | web.socketHandler(socket); 336 | socket.emit('message', 'data'); 337 | }); 338 | 339 | it('on message should NOT call socketMessageHandler if not enabled', function(done) { 340 | web = new WebService({node: node, enableSocketRPC: false}); 341 | web.eventNames = web.getEventNames(); 342 | web.socketMessageHandler = sinon.stub(); 343 | socket = new EventEmitter(); 344 | socket.conn = {}; 345 | socket.conn.remoteAddress = '127.0.0.1'; 346 | socket.client = {}; 347 | socket.client.request = {}; 348 | socket.client.request.headers = {}; 349 | web.socketHandler(socket); 350 | socket.on('message', function() { 351 | web.socketMessageHandler.callCount.should.equal(0); 352 | done(); 353 | }); 354 | socket.emit('message', 'data'); 355 | }); 356 | 357 | it('on subscribe should call bus.subscribe', function(done) { 358 | bus.subscribe = function(param1) { 359 | log.info.callCount.should.equal(1); 360 | param1.should.equal('data'); 361 | done(); 362 | }; 363 | 364 | socket.emit('subscribe', 'data'); 365 | }); 366 | 367 | it('on unsubscribe should call bus.unsubscribe', function(done) { 368 | bus.unsubscribe = function(param1) { 369 | param1.should.equal('data'); 370 | log.info.callCount.should.equal(1); 371 | done(); 372 | }; 373 | 374 | socket.emit('unsubscribe', 'data'); 375 | }); 376 | 377 | it('publish events from bus should be emitted from socket', function(done) { 378 | socket.once('event2', function(param1, param2) { 379 | param1.should.equal('param1'); 380 | param2.should.equal('param2'); 381 | done(); 382 | }); 383 | socket.connected = true; 384 | bus.emit('event2', 'param1', 'param2'); 385 | }); 386 | 387 | it('on disconnect should close bus', function(done) { 388 | bus.close = function() { 389 | done(); 390 | }; 391 | 392 | socket.emit('disconnect'); 393 | }); 394 | }); 395 | 396 | describe('#socketMessageHandler', function() { 397 | var node = { 398 | on: sinon.spy() 399 | }; 400 | 401 | var web = new WebService({node: node}); 402 | web.methodsMap = { 403 | one: { 404 | fn: function(param1, param2, callback) { 405 | var result = param1 + param2; 406 | if(result > 0) { 407 | return callback(null, result); 408 | } else { 409 | return callback(new Error('error')); 410 | } 411 | }, 412 | args: 2 413 | } 414 | }; 415 | 416 | it('should give a Method Not Found error if method does not exist', function(done) { 417 | var message = { 418 | method: 'two', 419 | params: [1, 2] 420 | }; 421 | web.socketMessageHandler(message, function(response) { 422 | should.exist(response.error); 423 | response.error.message.should.equal('Method Not Found'); 424 | done(); 425 | }); 426 | }); 427 | 428 | it('should call the method and return the result', function(done) { 429 | var message = { 430 | method: 'one', 431 | params: [1, 2] 432 | }; 433 | web.socketMessageHandler(message, function(response) { 434 | should.not.exist(response.error); 435 | response.result.should.equal(3); 436 | done(); 437 | }); 438 | }); 439 | 440 | it('should give an error if there is a param count mismatch', function(done) { 441 | var message = { 442 | method: 'one', 443 | params: [1] 444 | }; 445 | web.socketMessageHandler(message, function(response) { 446 | should.exist(response.error); 447 | response.error.message.should.equal('Expected 2 parameter(s)'); 448 | done(); 449 | }); 450 | }); 451 | 452 | it('should give an error if the method gave an error', function(done) { 453 | var message = { 454 | method: 'one', 455 | params: [-1, -2] 456 | }; 457 | web.socketMessageHandler(message, function(response) { 458 | should.exist(response.error); 459 | response.error.message.should.equal('Error: error'); 460 | done(); 461 | }); 462 | }); 463 | }); 464 | 465 | describe('#deriveHttpsOptions', function() { 466 | it('should read key and cert from files specified', function() { 467 | var web = new WebService({ 468 | node: defaultNode, 469 | https: true, 470 | httpsOptions: { 471 | key: 'key', 472 | cert: 'cert' 473 | } 474 | }); 475 | 476 | web.transformHttpsOptions(); 477 | web.httpsOptions.key.should.equal('key-buffer'); 478 | web.httpsOptions.cert.should.equal('cert-buffer'); 479 | }); 480 | it('should throw an error if https is specified but key or cert is not specified', function() { 481 | var web = new WebService({ 482 | node: defaultNode, 483 | https: true, 484 | httpsOptions: { 485 | key: 'key' 486 | } 487 | }); 488 | 489 | (function() { 490 | web.transformHttpsOptions(); 491 | }).should.throw('Missing https options'); 492 | }); 493 | }); 494 | 495 | }); 496 | -------------------------------------------------------------------------------- /test/utils.unit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var should = require('chai').should(); 4 | var utils = require('../lib/utils'); 5 | 6 | describe('Utils', function() { 7 | 8 | describe('#isHash', function() { 9 | 10 | it('false for short string', function() { 11 | var a = utils.isHash('ashortstring'); 12 | a.should.equal(false); 13 | }); 14 | 15 | it('false for long string', function() { 16 | var a = utils.isHash('00000000000000000000000000000000000000000000000000000000000000000'); 17 | a.should.equal(false); 18 | }); 19 | 20 | it('false for correct length invalid char', function() { 21 | var a = utils.isHash('z000000000000000000000000000000000000000000000000000000000000000'); 22 | a.should.equal(false); 23 | }); 24 | 25 | it('false for invalid type (buffer)', function() { 26 | var a = utils.isHash(new Buffer('abcdef', 'hex')); 27 | a.should.equal(false); 28 | }); 29 | 30 | it('false for invalid type (number)', function() { 31 | var a = utils.isHash(123456); 32 | a.should.equal(false); 33 | }); 34 | 35 | it('true for hash', function() { 36 | var a = utils.isHash('fc63629e2106c3440d7e56751adc8cfa5266a5920c1b54b81565af25aec1998b'); 37 | a.should.equal(true); 38 | }); 39 | 40 | }); 41 | 42 | describe('#isSafeNatural', function() { 43 | 44 | it('false for float', function() { 45 | var a = utils.isSafeNatural(0.1); 46 | a.should.equal(false); 47 | }); 48 | 49 | it('false for string float', function() { 50 | var a = utils.isSafeNatural('0.1'); 51 | a.should.equal(false); 52 | }); 53 | 54 | it('false for string integer', function() { 55 | var a = utils.isSafeNatural('1'); 56 | a.should.equal(false); 57 | }); 58 | 59 | it('false for negative integer', function() { 60 | var a = utils.isSafeNatural(-1); 61 | a.should.equal(false); 62 | }); 63 | 64 | it('false for negative integer string', function() { 65 | var a = utils.isSafeNatural('-1'); 66 | a.should.equal(false); 67 | }); 68 | 69 | it('false for infinity', function() { 70 | var a = utils.isSafeNatural(Infinity); 71 | a.should.equal(false); 72 | }); 73 | 74 | it('false for NaN', function() { 75 | var a = utils.isSafeNatural(NaN); 76 | a.should.equal(false); 77 | }); 78 | 79 | it('false for unsafe number', function() { 80 | var a = utils.isSafeNatural(Math.pow(2, 53)); 81 | a.should.equal(false); 82 | }); 83 | 84 | it('true for positive integer', function() { 85 | var a = utils.isSafeNatural(1000); 86 | a.should.equal(true); 87 | }); 88 | 89 | }); 90 | 91 | describe('#startAtZero', function() { 92 | 93 | it('will set key to zero if not set', function() { 94 | var obj = {}; 95 | utils.startAtZero(obj, 'key'); 96 | obj.key.should.equal(0); 97 | }); 98 | 99 | it('not if already set', function() { 100 | var obj = { 101 | key: 10 102 | }; 103 | utils.startAtZero(obj, 'key'); 104 | obj.key.should.equal(10); 105 | }); 106 | 107 | it('not if set to false', function() { 108 | var obj = { 109 | key: false 110 | }; 111 | utils.startAtZero(obj, 'key'); 112 | obj.key.should.equal(false); 113 | }); 114 | 115 | it('not if set to undefined', function() { 116 | var obj = { 117 | key: undefined 118 | }; 119 | utils.startAtZero(obj, 'key'); 120 | should.equal(obj.key, undefined); 121 | }); 122 | 123 | it('not if set to null', function() { 124 | var obj = { 125 | key: null 126 | }; 127 | utils.startAtZero(obj, 'key'); 128 | should.equal(obj.key, null); 129 | }); 130 | 131 | }); 132 | 133 | describe('#parseParamsWithJSON', function() { 134 | it('will parse object', function() { 135 | var paramsArg = ['3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou', '{"start": 100, "end": 1}']; 136 | var params = utils.parseParamsWithJSON(paramsArg); 137 | params.should.deep.equal(['3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou', {start: 100, end: 1}]); 138 | }); 139 | it('will parse array', function() { 140 | var paramsArg = ['3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou', '[0, 1]']; 141 | var params = utils.parseParamsWithJSON(paramsArg); 142 | params.should.deep.equal(['3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou', [0, 1]]); 143 | }); 144 | it('will parse numbers', function() { 145 | var paramsArg = ['3', 0, 'b', '0', 0x12, '0.0001']; 146 | var params = utils.parseParamsWithJSON(paramsArg); 147 | params.should.deep.equal([3, 0, 'b', 0, 0x12, 0.0001]); 148 | }); 149 | }); 150 | 151 | }); 152 | --------------------------------------------------------------------------------