From 17b1bf15abc8a1ab0b10f3e6c1e7117a59a6fbc3 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 17 Aug 2015 14:16:43 -0400 Subject: [PATCH 01/17] Start of module architecture and CLI for node configuration. --- README.md | 51 +++++++++++++++++++++++++++++++++++++----- cli/bitcore-node.js | 54 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ 3 files changed, 101 insertions(+), 6 deletions(-) create mode 100755 cli/bitcore-node.js diff --git a/README.md b/README.md index 6f71a8d5..2e80207d 100644 --- a/README.md +++ b/README.md @@ -3,18 +3,57 @@ Bitcore Node A Node.js module that adds a native interface to Bitcoin Core for querying information about the Bitcoin blockchain. Bindings are linked to Bitcoin Core compiled as a static library. -## Install +## Getting Started + +Bitcore Node includes a Command Line Interface (CLI) for managing, configuring and interfacing with your Bitcore Node. At the minimum, your node can function with all of the features from Bitcoin Core running as a full node. However you can enable additional features to make your node more useful such as exposing new APIs, adding new indexes for addresses, running a block explorer and more. + +Here is how you can you install and start your node: ```bash -git clone https://github.com/bitpay/bitcore-node.git -cd bitcore-node -npm install +npm install -g bitcore-node@0.2.0-beta.4 +bitcore-node create mynode "My Node" +cd mynode +bitcore-node start ``` -Note: For convenience, we distribute binaries for x86_64 Linux and x86_64 Mac OS X. Upon npm install, the binaries for your platform will be downloaded. This greatly speeds up the process of using this project. If you don't want to compile the project for yourself, then please skip down to "Example Usage" section for next steps. Please see detailed instructions below for complete build details and dependencies needed for installation if you choose to build the project from source. + +This will install bitcore-node globally and add a `bitcore-node` command to your path and will provide the functionality for configuring your full node, including adding additional features. + +Note: For your convenience, we distribute binaries for x86_64 Linux and x86_64 Mac OS X. Upon npm install, the binaries for your platform will be downloaded. If you want to compile the project yourself, then please see the [Build & Install](#build--install) for full detailed instructions to build the project from source. + +To install additional features, you can use the bitcore-node command "add": + +```bash +bitcore-node stop +bitcore-node add address +bitcore-node start +``` + +This will configure your node with the necessary modules, and create the proper configuration file necessary for running your node. The above example will add the address module bitcore-node, and will then start running on start. Please be aware that in some cases adding a module will mean that a reindex is required that will take several hours as each block is analyzed and a database is created. Any module that requires such will prompt before installation. + +Third party modules can also be specified by using a git repository: + +```bash +bitcore-node add https://github.com/yourname/helloworld +``` + +Please see the [directory](doc/modules.md) for a partial list of modules for Bitcore Node. If you're interested in developing a module, please see the [Module Development Guide](doc/modules-development.md). + +## Using your Node + +Your node should be able to respond to commands via a command line utility. For example, with the address module added, you should be able to query for unspent outputs for any address: + +```bash +bitcore-node call getunspentoutputs "1HTxCVrXuthad6YW5895K98XmVsdMvvBSw" +bitcore-node call getbalance "1HTxCVrXuthad6YW5895K98XmVsdMvvBSw" +bitcore-node call sendtransaction "" +bitcore-node call help +``` + +The above are a few common examples of commands that can be run, however there are many more. Every module that is added can extend the interface to include new commands. Running `bitcore-node call help` will list all of the available commands that can be run against your node. ## Build & Install -There are two main parts of the build, compiling Bitcoin Core as a static library and the Node.js bindings. +This includes a detailed instructions for compiling. There are two main parts of the build, compiling Bitcoin Core as a static library and the Node.js bindings. ### Ubuntu 14.04 (Unix/Linux) diff --git a/cli/bitcore-node.js b/cli/bitcore-node.js new file mode 100755 index 00000000..fd285967 --- /dev/null +++ b/cli/bitcore-node.js @@ -0,0 +1,54 @@ +'use strict'; + +var program = require('commander'); +var version = require(__dirname + '/../package.json').version; + +program + .version(version) + .option('-d, --datadir', 'Database and configuration directory') + .option('-t, --testnet', 'Enable testnet network'); + +program + .command('create [name]') + .description('Create a new node') + .action(function(directory, name){ + console.log(directory, name); + }); + +program + .command('add ') + .alias('install') + .description('Install a module for the current node') + .action(function(module){ + console.log(module); + }).on('--help', function() { + console.log(' Examples:'); + console.log(); + console.log(' $ bitcore-node add wallet-service'); + console.log(' $ bitcore-node add insight-api'); + console.log(); + }); + +program + .command('start') + .option('-b', '--background', 'Will start in the background') + .description('Start the current node') + .action(function(){ + console.log('start'); + }); + +program + .command('stop') + .description('Stop the current node') + .action(function(){ + console.log('stop'); + }); + +program + .command('*') + .description('') + .action(function(env){ + program.help(); + }); + +program.parse(process.argv); diff --git a/package.json b/package.json index 1c264d86..955eea47 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "author": "BitPay ", "version": "0.2.0-dev", "main": "./index.js", + "bin": "./cli/bitcore-node.js", "repository": "git://github.com/bitpay/bitcore-node.git", "homepage": "https://github.com/bitpay/bitcore-node.js", "bugs": { @@ -46,6 +47,7 @@ "bindings": "^1.2.1", "bitcore": "^0.13.0", "chainlib": "^0.2.0", + "commander": "^2.8.1", "errno": "^0.1.2", "memdown": "^1.0.0", "mkdirp": "0.5.0", From 00e4eac14a989d4c33bec2ee38694bcdfcefd6d2 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Wed, 19 Aug 2015 16:14:44 -0400 Subject: [PATCH 02/17] Layout test cases and files for cli commands. --- cli/bitcore-node.js | 18 +++++- lib/scaffold/add.js | 2 + lib/scaffold/create.js | 71 ++++++++++++++++++++++++ lib/scaffold/find-config.js | 2 + lib/scaffold/start.js | 1 + test/scaffold/add.integration.js | 40 +++++++++++++ test/scaffold/create.integration.js | 62 +++++++++++++++++++++ test/scaffold/find-config.integration.js | 36 ++++++++++++ test/scaffold/start.integration.js | 28 ++++++++++ 9 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 lib/scaffold/add.js create mode 100644 lib/scaffold/create.js create mode 100644 lib/scaffold/find-config.js create mode 100644 lib/scaffold/start.js create mode 100644 test/scaffold/add.integration.js create mode 100644 test/scaffold/create.integration.js create mode 100644 test/scaffold/find-config.integration.js create mode 100644 test/scaffold/start.integration.js diff --git a/cli/bitcore-node.js b/cli/bitcore-node.js index fd285967..808be28c 100755 --- a/cli/bitcore-node.js +++ b/cli/bitcore-node.js @@ -2,6 +2,11 @@ var program = require('commander'); var version = require(__dirname + '/../package.json').version; +var create = require('../lib/scaffold/create'); +var add = require('../lib/scaffold/add'); +var start = require('../lib/scaffold/start'); +var stop = require('../lib/scaffold/stop'); +var findConfig = require('../lib/scaffold/find-config'); program .version(version) @@ -12,7 +17,9 @@ program .command('create [name]') .description('Create a new node') .action(function(directory, name){ - console.log(directory, name); + var config = findConfig(); + create(config, directory, name); + console.log('Successfully created node in directory: ', directory); }); program @@ -20,6 +27,9 @@ program .alias('install') .description('Install a module for the current node') .action(function(module){ + var config = findConfig(); + add(config, module); + console.log('Successfully added module: ', module); console.log(module); }).on('--help', function() { console.log(' Examples:'); @@ -34,14 +44,16 @@ program .option('-b', '--background', 'Will start in the background') .description('Start the current node') .action(function(){ - console.log('start'); + var config = findConfig(); + start(config); }); program .command('stop') .description('Stop the current node') .action(function(){ - console.log('stop'); + var config = findConfig(); + stop(config); }); program diff --git a/lib/scaffold/add.js b/lib/scaffold/add.js new file mode 100644 index 00000000..eb109abb --- /dev/null +++ b/lib/scaffold/add.js @@ -0,0 +1,2 @@ +'use strict'; + diff --git a/lib/scaffold/create.js b/lib/scaffold/create.js new file mode 100644 index 00000000..1e0b7dcc --- /dev/null +++ b/lib/scaffold/create.js @@ -0,0 +1,71 @@ +'use strict'; + +var bitcore = require('bitcore'); +var version = require('../../package.json').version; +var mkdirp = require('mkdirp'); +var fs = require('fs'); + +var BASE_CONFIG = { + name: 'My Node', + modules: [ + 'address' + ], + datadir: './data', + network: 'livenet' +}; + +var BASE_PACKAGE = { + dependencies: { + 'bitcore': '^' + bitcore.version, + 'bitcore-node': '^' + version + } +}; + +var BASE_BITCOIN_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n'; + +function create(baseDirectory, dirname, name, done) { + + if (!baseDirectory) { + baseDirectory = process.cwd; + } + + var directory = baseDirectory + '/' + dirname; + + mkdirp(directory, function(err) { + if (err) { + throw err; + } + + // setup the configuration files + var config = BASE_CONFIG; + config.name = name; + var configJSON = JSON.stringify(config, null, 2); + var packageJSON = JSON.stringify(BASE_PACKAGE, null, 2); + try { + fs.writeFileSync(directory + '/bitcore-node.json', configJSON); + fs.writeFileSync(directory + '/package.json', packageJSON); + } catch(e) { + done(e); + } + + // setup the bitcoin data directory + mkdirp(directory + '/data', function(err) { + if (err) { + throw err; + } + + try { + fs.writeFileSync(directory + '/data/bitcoin.conf', BASE_BITCOIN_CONFIG); + } catch(e) { + done(e); + } + + done(); + + }); + + }); + +} + +module.exports = create; diff --git a/lib/scaffold/find-config.js b/lib/scaffold/find-config.js new file mode 100644 index 00000000..eb109abb --- /dev/null +++ b/lib/scaffold/find-config.js @@ -0,0 +1,2 @@ +'use strict'; + diff --git a/lib/scaffold/start.js b/lib/scaffold/start.js new file mode 100644 index 00000000..ad9a93a7 --- /dev/null +++ b/lib/scaffold/start.js @@ -0,0 +1 @@ +'use strict'; diff --git a/test/scaffold/add.integration.js b/test/scaffold/add.integration.js new file mode 100644 index 00000000..73a0d2c0 --- /dev/null +++ b/test/scaffold/add.integration.js @@ -0,0 +1,40 @@ +'use strict'; + +var should = require('chai').should(); +var sinon = require('sinon'); + +describe('#add', function() { + + before(function() { + // setup testing directories + }); + + after(function() { + // cleanup testing directories + }); + + describe('will modify scaffold files', function() { + + it('will give an error if expected files do not exist', function() { + + }); + + it('will update bitcore-node.json modules', function() { + + }); + + it('will update package.json modules', function() { + + }); + + it('will install the necessary node.js modules', function() { + + }); + + it('will install dependencies', function() { + + }); + + }); + +}); diff --git a/test/scaffold/create.integration.js b/test/scaffold/create.integration.js new file mode 100644 index 00000000..202fd9a7 --- /dev/null +++ b/test/scaffold/create.integration.js @@ -0,0 +1,62 @@ +'use strict'; + +var should = require('chai').should(); +var create = require('../../lib/scaffold/create'); +var fs = require('fs'); +var mkdirp = require('mkdirp'); +var rimraf = require('rimraf'); + +describe('#create', function() { + + var basePath = __dirname + '/../'; + var testDir = basePath + 'temporary-test-data'; + + before(function(done) { + // setup testing directories + mkdirp(testDir, function(err) { + if (err) { + throw err; + } + done(); + }); + }); + + after(function(done) { + // cleanup testing directories + rimraf(testDir, function(err) { + if (err) { + throw err; + } + done(); + }); + }); + + it('will create scaffold files', function() { + + create(testDir, 'mynode', 'My Node 1', function(err) { + if (err) { + throw err; + } + + var configPath = testDir + '/mynode/bitcore-node.json'; + var packagePath = testDir + '/mynode/package.json'; + var bitcoinConfig = testDir + '/mynode/data/bitcoin.conf'; + + should.equal(fs.existsSync(configPath), true); + should.equal(fs.existsSync(packagePath), true); + should.equal(fs.existsSync(bitcoinConfig), true); + + var config = JSON.parse(fs.readFileSync(configPath)); + config.name.should.equal('My Node 1'); + config.modules.should.deep.equal(['address']); + config.datadir.should.equal('./data'); + config.network.should.equal('livenet'); + + var pack = JSON.parse(fs.readFileSync(packagePath)); + should.exist(pack.dependencies); + + }); + + }); + +}); diff --git a/test/scaffold/find-config.integration.js b/test/scaffold/find-config.integration.js new file mode 100644 index 00000000..17cb4621 --- /dev/null +++ b/test/scaffold/find-config.integration.js @@ -0,0 +1,36 @@ +'use strict'; + +var should = require('chai').should(); +var sinon = require('sinon'); + +describe('#findConfig', function() { + + before(function() { + // setup testing directories + }); + + after(function() { + // cleanup testing directories + }); + + describe('will find a configuration file', function() { + + it('in the current directory', function() { + + }); + + it('in a parent directory', function() { + + }); + + it('recursively find in parent directories', function() { + + }); + + }); + + it('will fallback to a default location in the home directory', function() { + + }); + +}); diff --git a/test/scaffold/start.integration.js b/test/scaffold/start.integration.js new file mode 100644 index 00000000..8b7ec2bf --- /dev/null +++ b/test/scaffold/start.integration.js @@ -0,0 +1,28 @@ +'use strict'; + +var should = require('chai').should(); +var sinon = require('sinon'); + +describe('#start', function() { + + before(function() { + // setup testing directories + }); + + after(function() { + // cleanup testing directories + }); + + describe('will dynamically create a node from a configuration', function() { + + it('require each bitcore-node module', function() { + + }); + + it('create an instance of node with modules enabled', function() { + + }); + + }); + +}); From 399d379ff5ab6b2412250d38b60175887781f310 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 20 Aug 2015 12:34:26 -0400 Subject: [PATCH 03/17] More tests for config creation. --- lib/scaffold/create.js | 109 +++++++++++++++++++++------- test/scaffold/create.integration.js | 74 ++++++++++++++++++- 2 files changed, 153 insertions(+), 30 deletions(-) diff --git a/lib/scaffold/create.js b/lib/scaffold/create.js index 1e0b7dcc..18703f4a 100644 --- a/lib/scaffold/create.js +++ b/lib/scaffold/create.js @@ -1,6 +1,9 @@ 'use strict'; var bitcore = require('bitcore'); +var $ = bitcore.util.preconditions; +var _ = bitcore.deps._; +var path = require('path'); var version = require('../../package.json').version; var mkdirp = require('mkdirp'); var fs = require('fs'); @@ -23,48 +26,98 @@ var BASE_PACKAGE = { var BASE_BITCOIN_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n'; -function create(baseDirectory, dirname, name, done) { - - if (!baseDirectory) { - baseDirectory = process.cwd; - } - - var directory = baseDirectory + '/' + dirname; - - mkdirp(directory, function(err) { +/** + * Will create a directory and bitcoin.conf file for Bitcoin. + * @param {String} dataDir - The absolute path + * @param {Function} done - The callback function called when finished + */ +function createBitcoinDirectory(datadir, done) { + mkdirp(datadir, function(err) { + if (err) { + throw err; + } + + try { + fs.writeFileSync(datadir + '/bitcoin.conf', BASE_BITCOIN_CONFIG); + } catch(e) { + done(e); + } + done(); + }); +} + +/** + * Will create a base Bitcore Node configuration directory and files. + * @param {String} configDir - The absolute path + * @param {String} name - The name of the node + * @param {Function} done - The callback function called when finished + */ +function createConfigDirectory(configDir, name, isGlobal, done) { + mkdirp(configDir, function(err) { if (err) { throw err; } - // setup the configuration files var config = BASE_CONFIG; config.name = name; var configJSON = JSON.stringify(config, null, 2); var packageJSON = JSON.stringify(BASE_PACKAGE, null, 2); try { - fs.writeFileSync(directory + '/bitcore-node.json', configJSON); - fs.writeFileSync(directory + '/package.json', packageJSON); + fs.writeFileSync(configDir + '/bitcore-node.json', configJSON); + if (!isGlobal) { + fs.writeFileSync(configDir + '/package.json', packageJSON); + } } catch(e) { done(e); } - - // setup the bitcoin data directory - mkdirp(directory + '/data', function(err) { - if (err) { - throw err; - } - - try { - fs.writeFileSync(directory + '/data/bitcoin.conf', BASE_BITCOIN_CONFIG); - } catch(e) { - done(e); - } - - done(); - - }); + done(); }); +} + +/** + * @param {Object} options + * @param {String} options.cwd - The current working directory + * @param {String} options.dirname - The name of the bitcore node configuration directory + * @param {String} options.name - The name of the bitcore node + * @param {String} options.datadir - The path to the bitcoin datadir + * @param {Function} done - A callback function called when finished + */ +function create(options, done) { + /* jshint maxstatements:20 */ + + $.checkArgument(_.isObject(options)); + $.checkArgument(_.isFunction(done)); + $.checkArgument(_.isString(options.cwd)); + $.checkArgument(_.isString(options.dirname)); + $.checkArgument(_.isString(options.name)); + $.checkArgument(_.isBoolean(options.isGlobal)); + $.checkArgument(_.isString(options.datadir)); + + var cwd = options.cwd; + var dirname = options.dirname; + var name = options.name; + var datadir = options.datadir; + var isGlobal = options.isGlobal; + + if (!cwd) { + cwd = process.cwd; + } + + var absConfigDir = path.resolve(cwd, dirname); + var absDataDir = path.resolve(absConfigDir, datadir); + + if (!fs.existsSync(absConfigDir)) { + createConfigDirectory(absConfigDir, name, isGlobal, function() { + if (!fs.existsSync(absDataDir)) { + createBitcoinDirectory(absDataDir, done); + } else { + done(); + } + }); + } else { + done(new Error('Directory "' + absConfigDir+ '" already exists.')); + } } diff --git a/test/scaffold/create.integration.js b/test/scaffold/create.integration.js index 202fd9a7..f14a254e 100644 --- a/test/scaffold/create.integration.js +++ b/test/scaffold/create.integration.js @@ -17,7 +17,12 @@ describe('#create', function() { if (err) { throw err; } - done(); + mkdirp(testDir + '/.bitcoin', function(err) { + if (err) { + throw err; + } + done(); + }); }); }); @@ -33,7 +38,13 @@ describe('#create', function() { it('will create scaffold files', function() { - create(testDir, 'mynode', 'My Node 1', function(err) { + create({ + cwd: testDir, + dirname: 'mynode', + name: 'My Node 1', + isGlobal: false, + datadir: './data' + }, function(err) { if (err) { throw err; } @@ -59,4 +70,63 @@ describe('#create', function() { }); + it('will error if directory already exists', function() { + + create({ + cwd: testDir, + dirname: 'mynode', + name: 'My Node 2', + isGlobal: false, + datadir: './data' + }, function(err) { + should.exist(err); + err.message.should.match(/^Directory/); + }); + + }); + + it('will not create bitcoin.conf if it already exists', function() { + + create({ + cwd: testDir, + dirname: 'mynode2', + name: 'My Node 2', + isGlobal: false, + datadir: '../.bitcoin' + }, function(err) { + if (err) { + throw err; + } + + var bitcoinConfig = testDir + '/.bitcoin/bitcoin.conf'; + should.equal(fs.existsSync(bitcoinConfig), false); + + }); + + }); + + it('will not create a package.json if globally installed', function() { + + create({ + cwd: testDir, + dirname: 'mynode3', + name: 'My Node 3', + isGlobal: true, + datadir: '../.bitcoin' + }, function(err) { + if (err) { + throw err; + } + + if (err) { + throw err; + } + + var packagePath = testDir + '/mynode3/package.json'; + should.equal(fs.existsSync(packagePath), false); + + }); + + }); + }); From 67a2035365cdff750f5748c1b1fedcf0c2e58cf3 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 20 Aug 2015 14:53:07 -0400 Subject: [PATCH 04/17] Run `npm install` if the node is created locally. --- cli/bitcore-node.js | 54 +++++++++++----------- lib/scaffold/create.js | 70 ++++++++++++++++++++++------- test/scaffold/create.integration.js | 18 +++++++- 3 files changed, 96 insertions(+), 46 deletions(-) diff --git a/cli/bitcore-node.js b/cli/bitcore-node.js index 808be28c..52e17bc3 100755 --- a/cli/bitcore-node.js +++ b/cli/bitcore-node.js @@ -5,7 +5,6 @@ var version = require(__dirname + '/../package.json').version; var create = require('../lib/scaffold/create'); var add = require('../lib/scaffold/add'); var start = require('../lib/scaffold/start'); -var stop = require('../lib/scaffold/stop'); var findConfig = require('../lib/scaffold/find-config'); program @@ -16,10 +15,29 @@ program program .command('create [name]') .description('Create a new node') - .action(function(directory, name){ + .action(function(dirname, name){ + var options = { + cwd: process.cwd(), + dirname: dirname, + name: name, + datadir: './data', + isGlobal: false + }; + create(options, function(err) { + if (err) { + throw err; + } + console.log('Successfully created node in directory: ', dirname); + }); + }); + +program + .command('start') + .option('-b', '--background', 'Will start in the background') + .description('Start the current node') + .action(function(){ var config = findConfig(); - create(config, directory, name); - console.log('Successfully created node in directory: ', directory); + start(config); }); program @@ -39,28 +57,8 @@ program console.log(); }); -program - .command('start') - .option('-b', '--background', 'Will start in the background') - .description('Start the current node') - .action(function(){ - var config = findConfig(); - start(config); - }); - -program - .command('stop') - .description('Stop the current node') - .action(function(){ - var config = findConfig(); - stop(config); - }); - -program - .command('*') - .description('') - .action(function(env){ - program.help(); - }); - program.parse(process.argv); + +if (process.argv.length === 2) { + program.help(); +} diff --git a/lib/scaffold/create.js b/lib/scaffold/create.js index 18703f4a..ba3714d2 100644 --- a/lib/scaffold/create.js +++ b/lib/scaffold/create.js @@ -1,10 +1,12 @@ 'use strict'; +var spawn = require('child_process').spawn; var bitcore = require('bitcore'); +var async = require('async'); var $ = bitcore.util.preconditions; var _ = bitcore.deps._; var path = require('path'); -var version = require('../../package.json').version; +var packageFile = require('../../package.json'); var mkdirp = require('mkdirp'); var fs = require('fs'); @@ -17,10 +19,18 @@ var BASE_CONFIG = { network: 'livenet' }; +var version; +if (packageFile.version.match('-dev')) { + // Use the latest release (todo: update to find the latest release?) + version = '^0.2.0-beta.3'; +} else { + version = '^' + packageFile.version; +} + var BASE_PACKAGE = { dependencies: { 'bitcore': '^' + bitcore.version, - 'bitcore-node': '^' + version + 'bitcore-node': version } }; @@ -50,6 +60,7 @@ function createBitcoinDirectory(datadir, done) { * Will create a base Bitcore Node configuration directory and files. * @param {String} configDir - The absolute path * @param {String} name - The name of the node + * @param {Boolean} isGlobal - If the configuration depends on globally installed node modules. * @param {Function} done - The callback function called when finished */ function createConfigDirectory(configDir, name, isGlobal, done) { @@ -59,7 +70,7 @@ function createConfigDirectory(configDir, name, isGlobal, done) { } var config = BASE_CONFIG; - config.name = name; + config.name = name || 'Bitcore Node'; var configJSON = JSON.stringify(config, null, 2); var packageJSON = JSON.stringify(BASE_PACKAGE, null, 2); try { @@ -76,6 +87,9 @@ function createConfigDirectory(configDir, name, isGlobal, done) { } /** + * Will setup a directory with a Bitcore Node directory, configuration file, + * bitcoin configuration, and will install all necessary dependencies. + * * @param {Object} options * @param {String} options.cwd - The current working directory * @param {String} options.dirname - The name of the bitcore node configuration directory @@ -90,7 +104,7 @@ function create(options, done) { $.checkArgument(_.isFunction(done)); $.checkArgument(_.isString(options.cwd)); $.checkArgument(_.isString(options.dirname)); - $.checkArgument(_.isString(options.name)); + $.checkArgument(_.isString(options.name) || _.isUndefined(options.name)); $.checkArgument(_.isBoolean(options.isGlobal)); $.checkArgument(_.isString(options.datadir)); @@ -100,24 +114,46 @@ function create(options, done) { var datadir = options.datadir; var isGlobal = options.isGlobal; - if (!cwd) { - cwd = process.cwd; - } - var absConfigDir = path.resolve(cwd, dirname); var absDataDir = path.resolve(absConfigDir, datadir); - if (!fs.existsSync(absConfigDir)) { - createConfigDirectory(absConfigDir, name, isGlobal, function() { - if (!fs.existsSync(absDataDir)) { - createBitcoinDirectory(absDataDir, done); + async.series([ + function(next) { + if (!fs.existsSync(absConfigDir)) { + createConfigDirectory(absConfigDir, name, isGlobal, next); } else { - done(); + next(new Error('Directory "' + absConfigDir+ '" already exists.')); } - }); - } else { - done(new Error('Directory "' + absConfigDir+ '" already exists.')); - } + }, + function(next) { + if (!fs.existsSync(absDataDir)) { + createBitcoinDirectory(absDataDir, next); + } else { + next(); + } + }, + function(next) { + if (!isGlobal) { + var npm = spawn('npm', ['install'], {cwd: absConfigDir}); + + npm.stdout.on('data', function (data) { + process.stdout.write(data); + }); + + npm.stderr.on('data', function (data) { + process.stderr.write(data); + }); + + npm.on('close', function (code) { + //todo: handle code + next(); + }); + + } else { + next(); + } + } + ], done); } diff --git a/test/scaffold/create.integration.js b/test/scaffold/create.integration.js index f14a254e..827662eb 100644 --- a/test/scaffold/create.integration.js +++ b/test/scaffold/create.integration.js @@ -1,7 +1,23 @@ 'use strict'; var should = require('chai').should(); -var create = require('../../lib/scaffold/create'); +var proxyquire = require('proxyquire'); +var sinon = require('sinon'); +var create = proxyquire('../../lib/scaffold/create', { + 'child_process': { + spawn: sinon.stub().returns({ + stdout: { + on: sinon.stub() + }, + stderr: { + on: sinon.stub() + }, + on: function(event, cb) { + cb(); + } + }) + } +}); var fs = require('fs'); var mkdirp = require('mkdirp'); var rimraf = require('rimraf'); From 0b4af2757b1f271ba6a41bc72f41644d2df2ad1e Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 20 Aug 2015 15:43:13 -0400 Subject: [PATCH 05/17] Implement function to find configuration in the current path. --- lib/scaffold/create.js | 3 ++ lib/scaffold/find-config.js | 27 +++++++++++ test/scaffold/find-config.integration.js | 59 ++++++++++++++++++++---- 3 files changed, 81 insertions(+), 8 deletions(-) diff --git a/lib/scaffold/create.js b/lib/scaffold/create.js index ba3714d2..b5c01a75 100644 --- a/lib/scaffold/create.js +++ b/lib/scaffold/create.js @@ -119,6 +119,7 @@ function create(options, done) { async.series([ function(next) { + // Setup the the bitcore-node directory and configuration if (!fs.existsSync(absConfigDir)) { createConfigDirectory(absConfigDir, name, isGlobal, next); } else { @@ -126,6 +127,7 @@ function create(options, done) { } }, function(next) { + // Setup the bitcoin directory and configuration if (!fs.existsSync(absDataDir)) { createBitcoinDirectory(absDataDir, next); } else { @@ -133,6 +135,7 @@ function create(options, done) { } }, function(next) { + // Install all of the necessary dependencies if (!isGlobal) { var npm = spawn('npm', ['install'], {cwd: absConfigDir}); diff --git a/lib/scaffold/find-config.js b/lib/scaffold/find-config.js index eb109abb..6a947ad7 100644 --- a/lib/scaffold/find-config.js +++ b/lib/scaffold/find-config.js @@ -1,2 +1,29 @@ 'use strict'; +var bitcore = require('bitcore'); +var $ = bitcore.util.preconditions; +var _ = bitcore.deps._; +var path = require('path'); +var fs = require('fs'); + +/** + * Will return the path and bitcore-node configuration + * @param {String} cwd - The absolute path to the current working directory + */ +function findConfig(cwd) { + $.checkArgument(_.isString(cwd), 'Argument should be a string'); + $.checkArgument(path.isAbsolute(cwd), 'Argument should be an absolute path'); + var directory = String(cwd); + while (!fs.existsSync(path.resolve(directory, 'bitcore-node.json'))) { + directory = path.resolve(directory, '../'); + if (directory === '/') { + return false; + } + } + return { + path: directory, + config: require(path.resolve(directory, 'bitcore-node.json')) + }; +} + +module.exports = findConfig; diff --git a/test/scaffold/find-config.integration.js b/test/scaffold/find-config.integration.js index 17cb4621..c1032736 100644 --- a/test/scaffold/find-config.integration.js +++ b/test/scaffold/find-config.integration.js @@ -1,36 +1,79 @@ 'use strict'; +var fs = require('fs'); +var path = require('path'); var should = require('chai').should(); var sinon = require('sinon'); +var mkdirp = require('mkdirp'); +var rimraf = require('rimraf'); + +var findConfig = require('../../lib/scaffold/find-config'); describe('#findConfig', function() { - before(function() { + var testDir = path.resolve(__dirname, '../temporary-test-data'); + var expectedConfig = { + name: 'My Node' + }; + + before(function(done) { // setup testing directories + mkdirp(testDir + '/p2/p1/p0', function(err) { + if (err) { + throw err; + } + fs.writeFile( + testDir + '/p2/bitcore-node.json', + JSON.stringify(expectedConfig), + function() { + mkdirp(testDir + '/e0', function(err) { + if (err) { + throw err; + } + done(); + }); + } + ); + }); + }); - after(function() { + after(function(done) { // cleanup testing directories + rimraf(testDir, function(err) { + if (err) { + throw err; + } + done(); + }); }); - + + describe('will find a configuration file', function() { it('in the current directory', function() { - + var config = findConfig(path.resolve(testDir, 'p2')); + config.path.should.equal(path.resolve(testDir, 'p2')); + config.config.should.deep.equal(expectedConfig); }); it('in a parent directory', function() { - + var config = findConfig(path.resolve(testDir, 'p2/p1')); + config.path.should.equal(path.resolve(testDir, 'p2')); + config.config.should.deep.equal(expectedConfig); }); it('recursively find in parent directories', function() { - + var config = findConfig(path.resolve(testDir, 'p2/p1/p0')); + config.path.should.equal(path.resolve(testDir, 'p2')); + config.config.should.deep.equal(expectedConfig); }); }); - it('will fallback to a default location in the home directory', function() { - + it('will return false if missing a configuration', function() { + var config = findConfig(path.resolve(testDir, 'e0')); + config.should.equal(false); }); }); From 69056db5297677e092e3095bf6f253ae8f038785 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 20 Aug 2015 17:32:25 -0400 Subject: [PATCH 06/17] Move start to scaffold start and pass the bitcore-node configuration. --- bin/start.js | 145 ++------------------------------ cli/bitcore-node.js | 9 +- lib/scaffold/create.js | 3 +- lib/scaffold/start.js | 182 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 197 insertions(+), 142 deletions(-) diff --git a/bin/start.js b/bin/start.js index 9673da85..bd98b5eb 100644 --- a/bin/start.js +++ b/bin/start.js @@ -1,144 +1,13 @@ 'use strict'; -var BitcoinNode = require('..').Node; -var chainlib = require('chainlib'); -var socketio = require('socket.io'); -var log = chainlib.log; -log.debug = function() {}; +var start = require('../lib/scaffold/start'); -var configuration = { - datadir: process.env.BITCORENODE_DIR || '~/.bitcoin', - network: process.env.BITCORENODE_NETWORK || 'livenet', - port: 3000 -}; - -var node = new BitcoinNode(configuration); - -var count = 0; -var interval = false; - -function logSyncStatus() { - log.info('Sync Status: Tip:', node.chain.tip.hash, 'Height:', node.chain.tip.__height, 'Rate:', count/10, 'blocks per second'); -} - -node.on('synced', function() { - // Stop logging of sync status - clearInterval(interval); - interval = false; - logSyncStatus(); -}); - -node.on('ready', function() { - - var io = socketio(configuration.port); - - io.on('connection', function(socket) { - - var bus = node.openBus(); - - var methods = node.getAllAPIMethods(); - var methodsMap = {}; - - methods.forEach(function(data) { - var name = data[0]; - var instance = data[1]; - var method = data[2]; - var args = data[3]; - methodsMap[name] = { - fn: function() { - return method.apply(instance, arguments); - }, - args: args - }; - }); - - socket.on('message', function(message, socketCallback) { - if (methodsMap[message.method]) { - var params = message.params; - - if(!params || !params.length) { - params = []; - } - - if(params.length !== methodsMap[message.method].args) { - return socketCallback({ - error: { - message: 'Expected ' + methodsMap[message.method].args + ' parameters' - } - }); - } - - var callback = function(err, result) { - var response = {}; - if(err) { - response.error = { - message: err.toString() - }; - } - - if(result) { - response.result = result; - } - - socketCallback(response); - }; - - params = params.concat(callback); - methodsMap[message.method].fn.apply(this, params); - } else { - socketCallback({ - error: { - message: 'Method Not Found' - } - }); - } - }); - - socket.on('subscribe', function(name, params) { - bus.subscribe(name, params); - }); - - socket.on('unsubscribe', function(name, params) { - bus.unsubscribe(name, params); - }); - - var events = node.getAllPublishEvents(); - - events.forEach(function(event) { - bus.on(event.name, function() { - if(socket.connected) { - var results = []; - - for(var i = 0; i < arguments.length; i++) { - results.push(arguments[i]); - } - - var params = [event.name].concat(results); - socket.emit.apply(socket, params); - } - }); - }); - - socket.on('disconnect', function() { - bus.close(); - }); - - }); - -}); - -node.on('error', function(err) { - log.error(err); -}); - -node.chain.on('addblock', function(block) { - count++; - // Initialize logging if not already instantiated - if (!interval) { - interval = setInterval(function() { - logSyncStatus(); - count = 0; - }, 10000); +start({ + path: process.cwd(), + config: { + datadir: process.env.BITCORENODE_DIR || '~/.bitcoin', + network: process.env.BITCORENODE_NETWORK || 'livenet', + port: 3000 } }); diff --git a/cli/bitcore-node.js b/cli/bitcore-node.js index 52e17bc3..321cca4d 100755 --- a/cli/bitcore-node.js +++ b/cli/bitcore-node.js @@ -33,11 +33,14 @@ program program .command('start') - .option('-b', '--background', 'Will start in the background') .description('Start the current node') .action(function(){ - var config = findConfig(); - start(config); + var configInfo = findConfig(process.cwd()); + if (configInfo) { + start(configInfo); + } else { + throw new Error('Can not find bitcore-node.json in current path'); + } }); program diff --git a/lib/scaffold/create.js b/lib/scaffold/create.js index b5c01a75..f9dc2e6f 100644 --- a/lib/scaffold/create.js +++ b/lib/scaffold/create.js @@ -16,7 +16,8 @@ var BASE_CONFIG = { 'address' ], datadir: './data', - network: 'livenet' + network: 'livenet', + port: 3001 }; var version; diff --git a/lib/scaffold/start.js b/lib/scaffold/start.js index ad9a93a7..44324eb0 100644 --- a/lib/scaffold/start.js +++ b/lib/scaffold/start.js @@ -1 +1,183 @@ 'use strict'; + +var path = require('path'); +var socketio = require('socket.io'); +var BitcoreNode = require('../node'); +var chainlib = require('chainlib'); +var bitcore = require('bitcore'); +var _ = bitcore.deps._; +var log = chainlib.log; +log.debug = function() {}; + +var count = 0; +var interval = false; + +function start(options) { + /* jshint maxstatements: 100 */ + + var modules = []; + + var configPath = options.path; + var config = options.config; + + if (config.modules) { + for (var i = 0; i < config.modules.length; i++) { + var moduleName = config.modules[i]; + var module; + try { + // first try in the built-in bitcore-node modules directory + module = require(path.resolve(__dirname, '../modules/' + moduleName)); + } catch(e) { + // then try loading external modules + module = require(moduleName); + } + + modules.push(module); + } + } + + var fullConfig = _.clone(config); + + // expand to the full path + var datadir = config.datadir.replace(/^~/, process.env.HOME); + fullConfig.datadir = path.resolve(configPath, datadir); + + // load the modules + fullConfig.db = { + modules: modules + }; + + var node = new BitcoreNode(fullConfig); + + function logSyncStatus() { + log.info( + 'Sync Status: Tip:', node.chain.tip.hash, + 'Height:', node.chain.tip.__height, + 'Rate:', count/10, 'blocks per second' + ); + } + + node.on('synced', function() { + // Stop logging of sync status + clearInterval(interval); + interval = false; + logSyncStatus(); + }); + + node.on('ready', function() { + + var io = socketio(fullConfig.port); + + io.on('connection', function(socket) { + + var bus = node.openBus(); + + var methods = node.getAllAPIMethods(); + var methodsMap = {}; + + methods.forEach(function(data) { + var name = data[0]; + var instance = data[1]; + var method = data[2]; + var args = data[3]; + methodsMap[name] = { + fn: function() { + return method.apply(instance, arguments); + }, + args: args + }; + }); + + socket.on('message', function(message, socketCallback) { + if (methodsMap[message.method]) { + var params = message.params; + + if(!params || !params.length) { + params = []; + } + + if(params.length !== methodsMap[message.method].args) { + return socketCallback({ + error: { + message: 'Expected ' + methodsMap[message.method].args + ' parameters' + } + }); + } + + var callback = function(err, result) { + var response = {}; + if(err) { + response.error = { + message: err.toString() + }; + } + + if(result) { + response.result = result; + } + + socketCallback(response); + }; + + params = params.concat(callback); + methodsMap[message.method].fn.apply(this, params); + } else { + socketCallback({ + error: { + message: 'Method Not Found' + } + }); + } + }); + + socket.on('subscribe', function(name, params) { + bus.subscribe(name, params); + }); + + socket.on('unsubscribe', function(name, params) { + bus.unsubscribe(name, params); + }); + + var events = node.getAllPublishEvents(); + + events.forEach(function(event) { + bus.on(event.name, function() { + if(socket.connected) { + var results = []; + + for(var i = 0; i < arguments.length; i++) { + results.push(arguments[i]); + } + + var params = [event.name].concat(results); + socket.emit.apply(socket, params); + } + }); + }); + + socket.on('disconnect', function() { + bus.close(); + }); + + }); + + }); + + node.on('error', function(err) { + log.error(err); + }); + + node.chain.on('addblock', function(block) { + count++; + // Initialize logging if not already instantiated + if (!interval) { + interval = setInterval(function() { + logSyncStatus(); + count = 0; + }, 10000); + } + }); + +} + +module.exports = start; From 6c7501b45f76a2f0963f6cf04d63a4a315a99edb Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 20 Aug 2015 18:20:50 -0400 Subject: [PATCH 07/17] Include script env and include a build version. --- cli/bitcore-node.js | 2 ++ lib/scaffold/create.js | 3 +-- package.json | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/bitcore-node.js b/cli/bitcore-node.js index 321cca4d..d5c3d76e 100755 --- a/cli/bitcore-node.js +++ b/cli/bitcore-node.js @@ -1,3 +1,5 @@ +#!/usr/bin/env node + 'use strict'; var program = require('commander'); diff --git a/lib/scaffold/create.js b/lib/scaffold/create.js index f9dc2e6f..c57fb71e 100644 --- a/lib/scaffold/create.js +++ b/lib/scaffold/create.js @@ -22,8 +22,7 @@ var BASE_CONFIG = { var version; if (packageFile.version.match('-dev')) { - // Use the latest release (todo: update to find the latest release?) - version = '^0.2.0-beta.3'; + version = '^' + packageFile.lastBuild; } else { version = '^' + packageFile.version; } diff --git a/package.json b/package.json index 955eea47..c97f210c 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "description": "Full node with extended capabilities using Bitcore and Bitcoin Core", "author": "BitPay ", "version": "0.2.0-dev", + "lastBuild": "0.2.0-beta.3", "main": "./index.js", "bin": "./cli/bitcore-node.js", "repository": "git://github.com/bitpay/bitcore-node.git", From be525b055da00542b97863f624d117de297eecfa Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 20 Aug 2015 18:41:41 -0400 Subject: [PATCH 08/17] Replace ~/.bitcoin with process.env.HOME --- bin/start.js | 5 +++-- lib/node.js | 14 ++++++-------- lib/scaffold/start.js | 3 +-- test/node.unit.js | 10 +++++----- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/bin/start.js b/bin/start.js index bd98b5eb..72a59a0c 100644 --- a/bin/start.js +++ b/bin/start.js @@ -1,13 +1,14 @@ 'use strict'; var start = require('../lib/scaffold/start'); +var path = require('path'); start({ path: process.cwd(), config: { - datadir: process.env.BITCORENODE_DIR || '~/.bitcoin', + datadir: process.env.BITCORENODE_DIR || path.resolve(process.env.HOME, '.bitcoin'), network: process.env.BITCORENODE_NETWORK || 'livenet', - port: 3000 + port: process.env.BITCORENODE_PORT || 3001 } }); diff --git a/lib/node.js b/lib/node.js index 18712823..4218cd3e 100644 --- a/lib/node.js +++ b/lib/node.js @@ -57,12 +57,11 @@ Node.DEFAULT_DAEMON_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n'; Node.prototype._loadBitcoinConf = function(config) { $.checkArgument(config.datadir, 'Please specify "datadir" in configuration options'); - var datadir = config.datadir.replace(/^~/, process.env.HOME); - var configPath = datadir + '/bitcoin.conf'; + var configPath = config.datadir + '/bitcoin.conf'; this.bitcoinConfiguration = {}; - if (!fs.existsSync(datadir)) { - mkdirp.sync(datadir); + if (!fs.existsSync(config.datadir)) { + mkdirp.sync(config.datadir); } if (!fs.existsSync(configPath)) { @@ -329,13 +328,12 @@ Node.prototype._loadDB = function(config) { $.checkArgument(config.datadir, 'Please specify "datadir" in configuration options'); $.checkState(this.network, 'Network property not defined'); var regtest = Networks.get('regtest'); - var datadir = config.datadir.replace(/^~/, process.env.HOME); if (this.network === Networks.livenet) { - options.path = datadir + '/bitcore-node.db'; + config.db.path = config.datadir + '/bitcore-node.db'; } else if (this.network === Networks.testnet) { - options.path = datadir + '/testnet3/bitcore-node.db'; + config.db.path = config.datadir + '/testnet3/bitcore-node.db'; } else if (this.network === regtest) { - options.path = datadir + '/regtest/bitcore-node.db'; + config.db.path = config.datadir + '/regtest/bitcore-node.db'; } else { throw new Error('Unknown network: ' + this.network); } diff --git a/lib/scaffold/start.js b/lib/scaffold/start.js index 44324eb0..ff624006 100644 --- a/lib/scaffold/start.js +++ b/lib/scaffold/start.js @@ -39,8 +39,7 @@ function start(options) { var fullConfig = _.clone(config); // expand to the full path - var datadir = config.datadir.replace(/^~/, process.env.HOME); - fullConfig.datadir = path.resolve(configPath, datadir); + fullConfig.datadir = path.resolve(configPath, config.datadir); // load the modules fullConfig.db = { diff --git a/test/node.unit.js b/test/node.unit.js index 552b3e40..831c2959 100644 --- a/test/node.unit.js +++ b/test/node.unit.js @@ -101,7 +101,7 @@ describe('Bitcoind Node', function() { describe('#_loadBitcoinConf', function() { it('will parse a bitcoin.conf file', function() { var node = new Node({}); - node._loadBitcoinConf({datadir: '~/.bitcoin'}); + node._loadBitcoinConf({datadir: process.env.HOME + '/.bitcoin'}); should.exist(node.bitcoinConfiguration); node.bitcoinConfiguration.should.deep.equal({ server: 1, @@ -349,7 +349,7 @@ describe('Bitcoind Node', function() { }; var config = { DB: DB, - datadir: '~/.bitcoin' + datadir: process.env.HOME + '/.bitcoin' }; var node = new Node(config); @@ -363,7 +363,7 @@ describe('Bitcoind Node', function() { }; var config = { DB: DB, - datadir: '~/.bitcoin' + datadir: process.env.HOME + '/.bitcoin' }; var node = new Node(config); @@ -373,7 +373,7 @@ describe('Bitcoind Node', function() { }); it('error with unknown network', function() { var config = { - datadir: '~/.bitcoin' + datadir: process.env.HOME + '/.bitcoin' }; var node = new Node(config); @@ -388,7 +388,7 @@ describe('Bitcoind Node', function() { }; var config = { DB: DB, - datadir: '~/.bitcoin' + datadir: process.env.HOME + '/.bitcoin' }; var node = new Node(config); From 348598747bf1c1e393518aa5d149a0d7542c277e Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 20 Aug 2015 20:05:23 -0400 Subject: [PATCH 09/17] Include more options and fallback to default config with start command --- bin/start.js | 43 ++-------------------------------- cli/bitcore-node.js | 38 ++++++++++++++++++++---------- lib/scaffold/create.js | 6 +++-- lib/scaffold/default-config.js | 20 ++++++++++++++++ lib/scaffold/start.js | 31 ++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 56 deletions(-) create mode 100644 lib/scaffold/default-config.js diff --git a/bin/start.js b/bin/start.js index 72a59a0c..f03b672f 100644 --- a/bin/start.js +++ b/bin/start.js @@ -1,45 +1,6 @@ 'use strict'; var start = require('../lib/scaffold/start'); -var path = require('path'); +var defaultConfig = require('../lib/scaffold/default-config'); -start({ - path: process.cwd(), - config: { - datadir: process.env.BITCORENODE_DIR || path.resolve(process.env.HOME, '.bitcoin'), - network: process.env.BITCORENODE_NETWORK || 'livenet', - port: process.env.BITCORENODE_PORT || 3001 - } -}); - -node.on('stopping', function() { - clearInterval(interval); -}); - -function exitHandler(options, err) { - if (err) { - log.error('uncaught exception:', err); - if(err.stack) { - console.log(err.stack); - } - process.exit(-1); - } - if (options.sigint) { - node.stop(function(err) { - if(err) { - log.error('Failed to stop services: ' + err); - return process.exit(1); - } - - log.info('Halted'); - process.exit(0); - }); - } -} - -//catches uncaught exceptions - - -process.on('uncaughtException', exitHandler.bind(null, {exit:true})); -//catches ctrl+c event -process.on('SIGINT', exitHandler.bind(null, {sigint:true})); +start(defaultConfig()); diff --git a/cli/bitcore-node.js b/cli/bitcore-node.js index d5c3d76e..168cac74 100755 --- a/cli/bitcore-node.js +++ b/cli/bitcore-node.js @@ -4,28 +4,34 @@ var program = require('commander'); var version = require(__dirname + '/../package.json').version; +var bitcore = require('bitcore'); +var $ = bitcore.util.preconditions; +var path = require('path'); var create = require('../lib/scaffold/create'); var add = require('../lib/scaffold/add'); var start = require('../lib/scaffold/start'); var findConfig = require('../lib/scaffold/find-config'); +var defaultConfig = require('../lib/scaffold/default-config'); program - .version(version) - .option('-d, --datadir', 'Database and configuration directory') - .option('-t, --testnet', 'Enable testnet network'); + .version(version); program .command('create [name]') .description('Create a new node') - .action(function(dirname, name){ - var options = { + .option('-d, --datadir ', 'Specify the bitcoin database directory') + .action(function(dirname, name, cmd){ + if (cmd.datadir) { + cmd.datadir = path.resolve(process.cwd(), cmd.datadir); + } + var opts = { cwd: process.cwd(), dirname: dirname, name: name, - datadir: './data', + datadir: cmd.datadir || './data', isGlobal: false }; - create(options, function(err) { + create(opts, function(err) { if (err) { throw err; } @@ -36,13 +42,16 @@ program program .command('start') .description('Start the current node') - .action(function(){ - var configInfo = findConfig(process.cwd()); - if (configInfo) { - start(configInfo); - } else { - throw new Error('Can not find bitcore-node.json in current path'); + .option('-c, --config ', 'Specify the directory with Bitcore Node configuration') + .action(function(cmd){ + if (cmd.config) { + cmd.config = path.resolve(process.cwd(), cmd.config); } + var configInfo = findConfig(cmd.config || process.cwd()); + if (!configInfo) { + configInfo = defaultConfig(); + } + start(configInfo); }); program @@ -51,6 +60,9 @@ program .description('Install a module for the current node') .action(function(module){ var config = findConfig(); + if (!config) { + throw new Error('Could not find configuration, see `bitcore-node create --help`'); + } add(config, module); console.log('Successfully added module: ', module); console.log(module); diff --git a/lib/scaffold/create.js b/lib/scaffold/create.js index c57fb71e..d2d14594 100644 --- a/lib/scaffold/create.js +++ b/lib/scaffold/create.js @@ -60,10 +60,11 @@ function createBitcoinDirectory(datadir, done) { * Will create a base Bitcore Node configuration directory and files. * @param {String} configDir - The absolute path * @param {String} name - The name of the node + * @param {String} datadir - The bitcoin database directory * @param {Boolean} isGlobal - If the configuration depends on globally installed node modules. * @param {Function} done - The callback function called when finished */ -function createConfigDirectory(configDir, name, isGlobal, done) { +function createConfigDirectory(configDir, name, datadir, isGlobal, done) { mkdirp(configDir, function(err) { if (err) { throw err; @@ -71,6 +72,7 @@ function createConfigDirectory(configDir, name, isGlobal, done) { var config = BASE_CONFIG; config.name = name || 'Bitcore Node'; + config.datadir = datadir; var configJSON = JSON.stringify(config, null, 2); var packageJSON = JSON.stringify(BASE_PACKAGE, null, 2); try { @@ -121,7 +123,7 @@ function create(options, done) { function(next) { // Setup the the bitcore-node directory and configuration if (!fs.existsSync(absConfigDir)) { - createConfigDirectory(absConfigDir, name, isGlobal, next); + createConfigDirectory(absConfigDir, name, datadir, isGlobal, next); } else { next(new Error('Directory "' + absConfigDir+ '" already exists.')); } diff --git a/lib/scaffold/default-config.js b/lib/scaffold/default-config.js new file mode 100644 index 00000000..b35437b1 --- /dev/null +++ b/lib/scaffold/default-config.js @@ -0,0 +1,20 @@ +'use strict'; + +var path = require('path'); + +/** + * Will return the path and default bitcore-node configuration on environment variables + * or default locations. + */ +function getDefaultConfig() { + return { + path: process.cwd(), + config: { + datadir: process.env.BITCORENODE_DIR || path.resolve(process.env.HOME, '.bitcoin'), + network: process.env.BITCORENODE_NETWORK || 'livenet', + port: process.env.BITCORENODE_PORT || 3001 + } + }; +} + +module.exports = getDefaultConfig; diff --git a/lib/scaffold/start.js b/lib/scaffold/start.js index ff624006..b827aedc 100644 --- a/lib/scaffold/start.js +++ b/lib/scaffold/start.js @@ -177,6 +177,37 @@ function start(options) { } }); + node.on('stopping', function() { + clearInterval(interval); + }); + + function exitHandler(options, err) { + if (err) { + log.error('uncaught exception:', err); + if(err.stack) { + console.log(err.stack); + } + process.exit(-1); + } + if (options.sigint) { + node.stop(function(err) { + if(err) { + log.error('Failed to stop services: ' + err); + return process.exit(1); + } + + log.info('Halted'); + process.exit(0); + }); + } + } + + //catches uncaught exceptions + process.on('uncaughtException', exitHandler.bind(null, {exit:true})); + + //catches ctrl+c event + process.on('SIGINT', exitHandler.bind(null, {sigint:true})); + } module.exports = start; From 450fb8981b4f2bb92af04d0a3e9ded4c71f03b9d Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 20 Aug 2015 20:33:21 -0400 Subject: [PATCH 10/17] Update README with updated command options. --- README.md | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 2e80207d..7a89774d 100644 --- a/README.md +++ b/README.md @@ -3,40 +3,29 @@ Bitcore Node A Node.js module that adds a native interface to Bitcoin Core for querying information about the Bitcoin blockchain. Bindings are linked to Bitcoin Core compiled as a static library. -## Getting Started - -Bitcore Node includes a Command Line Interface (CLI) for managing, configuring and interfacing with your Bitcore Node. At the minimum, your node can function with all of the features from Bitcoin Core running as a full node. However you can enable additional features to make your node more useful such as exposing new APIs, adding new indexes for addresses, running a block explorer and more. +## Install Here is how you can you install and start your node: ```bash npm install -g bitcore-node@0.2.0-beta.4 -bitcore-node create mynode "My Node" -cd mynode bitcore-node start ``` -This will install bitcore-node globally and add a `bitcore-node` command to your path and will provide the functionality for configuring your full node, including adding additional features. - Note: For your convenience, we distribute binaries for x86_64 Linux and x86_64 Mac OS X. Upon npm install, the binaries for your platform will be downloaded. If you want to compile the project yourself, then please see the [Build & Install](#build--install) for full detailed instructions to build the project from source. -To install additional features, you can use the bitcore-node command "add": - -```bash -bitcore-node stop -bitcore-node add address -bitcore-node start -``` - -This will configure your node with the necessary modules, and create the proper configuration file necessary for running your node. The above example will add the address module bitcore-node, and will then start running on start. Please be aware that in some cases adding a module will mean that a reindex is required that will take several hours as each block is analyzed and a database is created. Any module that requires such will prompt before installation. - -Third party modules can also be specified by using a git repository: +## Configuration + +Bitcore Node includes a Command Line Interface (CLI) for managing, configuring and interfacing with your Bitcore Node. At the minimum, your node can function with all of the features from Bitcoin Core running as a full node. However you can enable additional features to make your node more useful such as exposing new APIs, adding new indexes for addresses, running a block explorer and custom modules. ```bash +bitcore-node create -d mynode "My Node" +cd mynode +bitcore-node add bitcore-node add https://github.com/yourname/helloworld ``` -Please see the [directory](doc/modules.md) for a partial list of modules for Bitcore Node. If you're interested in developing a module, please see the [Module Development Guide](doc/modules-development.md). +This will create a directory with configuration files for your node and install the necessary dependencies. Please see the [directory](doc/modules.md) for a partial list of modules for Bitcore Node. If you're interested in developing a module, please see the [Module Development Guide](doc/modules-development.md). ## Using your Node From af88cbe55f686effaebd9b0f65e346cb1bae0398 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 24 Aug 2015 19:00:31 -0400 Subject: [PATCH 11/17] Fix node units tests for _loadDB --- lib/node.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/node.js b/lib/node.js index 4218cd3e..45f21b7d 100644 --- a/lib/node.js +++ b/lib/node.js @@ -329,11 +329,11 @@ Node.prototype._loadDB = function(config) { $.checkState(this.network, 'Network property not defined'); var regtest = Networks.get('regtest'); if (this.network === Networks.livenet) { - config.db.path = config.datadir + '/bitcore-node.db'; + options.path = config.datadir + '/bitcore-node.db'; } else if (this.network === Networks.testnet) { - config.db.path = config.datadir + '/testnet3/bitcore-node.db'; + options.path = config.datadir + '/testnet3/bitcore-node.db'; } else if (this.network === regtest) { - config.db.path = config.datadir + '/regtest/bitcore-node.db'; + options.path = config.datadir + '/regtest/bitcore-node.db'; } else { throw new Error('Unknown network: ' + this.network); } From 5ea787b3a1ad644976fcae5ec87dc9e1f746b03f Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 25 Aug 2015 13:01:52 -0400 Subject: [PATCH 12/17] Added `add` command and tests. --- cli/bitcore-node.js | 18 +++-- lib/scaffold/add.js | 102 +++++++++++++++++++++++++++ test/scaffold/add.integration.js | 114 ++++++++++++++++++++++++++----- 3 files changed, 211 insertions(+), 23 deletions(-) diff --git a/cli/bitcore-node.js b/cli/bitcore-node.js index 168cac74..e53ffdb2 100755 --- a/cli/bitcore-node.js +++ b/cli/bitcore-node.js @@ -55,17 +55,21 @@ program }); program - .command('add ') + .command('add ') .alias('install') .description('Install a module for the current node') - .action(function(module){ - var config = findConfig(); - if (!config) { + .action(function(modules){ + var configInfo = findConfig(process.cwd()); + if (!configInfo) { throw new Error('Could not find configuration, see `bitcore-node create --help`'); } - add(config, module); - console.log('Successfully added module: ', module); - console.log(module); + var opts = { + path: configInfo.path, + modules: modules + }; + add(opts, function() { + console.log('Successfully added modules: ', modules.join(', ')); + }); }).on('--help', function() { console.log(' Examples:'); console.log(); diff --git a/lib/scaffold/add.js b/lib/scaffold/add.js index eb109abb..d8d159d9 100644 --- a/lib/scaffold/add.js +++ b/lib/scaffold/add.js @@ -1,2 +1,104 @@ 'use strict'; +var async = require('async'); +var fs = require('fs'); +var path = require('path'); +var spawn = require('child_process').spawn; +var bitcore = require('bitcore'); +var $ = bitcore.util.preconditions; +var _ = bitcore.deps._; + +/** + * @param {String} configFilePath - The absolute path to the configuration file + * @param {String} module - The name of the module + * @param {Function} done + */ +function addConfig(configFilePath, module, done) { + $.checkState(path.isAbsolute(configFilePath), 'An absolute path is expected'); + fs.readFile(configFilePath, function(err, data) { + if (err) { + return done(err); + } + var config = JSON.parse(data); + $.checkState( + Array.isArray(config.modules), + 'Configuration file is expected to have a modules array.' + ); + config.modules.push(module); + config.modules = _.unique(config.modules); + config.modules.sort(function(a, b) { + return a > b; + }); + fs.writeFile(configFilePath, JSON.stringify(config, null, 2), done); + }); +} + +/** + * @param {String} configDir - The absolute configuration directory path + * @param {String} module - The name of the module + * @param {Function} done + */ +function addModule(configDir, module, done) { + $.checkState(path.isAbsolute(configDir), 'An absolute path is expected'); + var npm = spawn('npm', ['install', module, '--save'], {cwd: configDir}); + + npm.stdout.on('data', function(data) { + process.stdout.write(data); + }); + + npm.stderr.on('data', function(data) { + process.stderr.write(data); + }); + + npm.on('close', function(code) { + if (code !== 0) { + return done(new Error('There was an error installing module: ' + module)); + } else { + return done(); + } + }); +} + +/** + * @param {String} options.cwd - The current working directory + * @param {String} options.dirname - The bitcore-node configuration directory + * @param {Array} options.modules - An array of strings of module names + * @param {Function} done - A callback function called when finished + */ +function add(options, done) { + $.checkArgument(_.isObject(options)); + $.checkArgument(_.isFunction(done)); + $.checkArgument( + _.isString(options.path) && path.isAbsolute(options.path), + 'An absolute path is expected' + ); + $.checkArgument(Array.isArray(options.modules)); + + var configPath = options.path; + var modules = options.modules; + + var bitcoreConfigPath = path.resolve(configPath, 'bitcore-node.json'); + var packagePath = path.resolve(configPath, 'package.json'); + + if (!fs.existsSync(bitcoreConfigPath) || !fs.existsSync(packagePath)) { + return done( + new Error('Directory does not have a bitcore-node.json and/or package.json file.') + ); + } + + async.eachSeries( + modules, + function(module, next) { + // npm install --save + addModule(configPath, module, function(err) { + if (err) { + return next(err); + } + // add module to bitcore-node.json + addConfig(bitcoreConfigPath, module, next); + }); + }, done + ); +} + +module.exports = add; diff --git a/test/scaffold/add.integration.js b/test/scaffold/add.integration.js index 73a0d2c0..bfea5917 100644 --- a/test/scaffold/add.integration.js +++ b/test/scaffold/add.integration.js @@ -2,37 +2,119 @@ var should = require('chai').should(); var sinon = require('sinon'); +var path = require('path'); +var fs = require('fs'); +var proxyquire = require('proxyquire'); +var mkdirp = require('mkdirp'); +var rimraf = require('rimraf'); +var add = require('../../lib/scaffold/add'); describe('#add', function() { - before(function() { - // setup testing directories + var basePath = path.resolve(__dirname, '..'); + var testDir = path.resolve(basePath, 'temporary-test-data'); + var startConfig = { + name: 'My Node', + modules: [] + }; + var startPackage = {}; + + before(function(done) { + mkdirp(testDir + '/s0/s1', function(err) { + if (err) { + throw err; + } + fs.writeFile( + testDir + '/s0/s1/bitcore-node.json', + JSON.stringify(startConfig), + function(err) { + if (err) { + throw err; + } + fs.writeFile( + testDir + '/s0/s1/package.json', + JSON.stringify(startPackage), + done + ); + } + ); + }); }); - after(function() { + after(function(done) { // cleanup testing directories + rimraf(testDir, function(err) { + if (err) { + throw err; + } + done(); + }); }); describe('will modify scaffold files', function() { - it('will give an error if expected files do not exist', function() { - + it('will give an error if expected files do not exist', function(done) { + add({ + path: path.resolve(testDir, 's0'), + modules: ['a', 'b', 'c'] + }, function(err) { + should.exist(err); + err.message.match(/^Invalid state/); + done(); + }); }); - it('will update bitcore-node.json modules', function() { + it('will receive error from `npm install`', function(done) { + var spawn = sinon.stub().returns({ + stdout: { + on: sinon.stub() + }, + stderr: { + on: sinon.stub() + }, + on: sinon.stub().callsArgWith(1, 1) + }); + var addtest = proxyquire('../../lib/scaffold/add', { + 'child_process': { + spawn: spawn + } + }); + addtest({ + path: path.resolve(testDir, 's0/s1/'), + modules: ['a', 'b', 'c'] + }, function(err) { + should.exist(err); + err.message.should.equal('There was an error installing module: a'); + done(); + }); }); - it('will update package.json modules', function() { - - }); - - it('will install the necessary node.js modules', function() { - - }); - - it('will install dependencies', function() { - + it('will update bitcore-node.json modules', function(done) { + var spawn = sinon.stub().returns({ + stdout: { + on: sinon.stub() + }, + stderr: { + on: sinon.stub() + }, + on: sinon.stub().callsArgWith(1, 0) + }); + var addtest = proxyquire('../../lib/scaffold/add', { + 'child_process': { + spawn: spawn + } + }); + addtest({ + path: path.resolve(testDir, 's0/s1/'), + modules: ['a', 'b', 'c'] + }, function(err) { + should.not.exist(err); + var configPath = path.resolve(testDir, 's0/s1/bitcore-node.json'); + var config = JSON.parse(fs.readFileSync(configPath)); + config.modules.should.deep.equal(['a','b','c']); + done(); + }); }); }); From 7e174fd0cd18ff0025c95e82e3e07d3acb8dc639 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 25 Aug 2015 13:15:30 -0400 Subject: [PATCH 13/17] Handle exit codes from npm install with the create command. --- lib/scaffold/create.js | 7 ++++-- test/scaffold/create.integration.js | 36 +++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/lib/scaffold/create.js b/lib/scaffold/create.js index d2d14594..22dcd954 100644 --- a/lib/scaffold/create.js +++ b/lib/scaffold/create.js @@ -150,8 +150,11 @@ function create(options, done) { }); npm.on('close', function (code) { - //todo: handle code - next(); + if (code !== 0) { + return next(new Error('There was an error installing dependencies.')); + } else { + return next(); + } }); } else { diff --git a/test/scaffold/create.integration.js b/test/scaffold/create.integration.js index 827662eb..8930d207 100644 --- a/test/scaffold/create.integration.js +++ b/test/scaffold/create.integration.js @@ -13,7 +13,7 @@ var create = proxyquire('../../lib/scaffold/create', { on: sinon.stub() }, on: function(event, cb) { - cb(); + cb(0); } }) } @@ -134,10 +134,6 @@ describe('#create', function() { throw err; } - if (err) { - throw err; - } - var packagePath = testDir + '/mynode3/package.json'; should.equal(fs.existsSync(packagePath), false); @@ -145,4 +141,34 @@ describe('#create', function() { }); + it('will receieve an error from npm', function() { + var createtest = proxyquire('../../lib/scaffold/create', { + 'child_process': { + spawn: sinon.stub().returns({ + stdout: { + on: sinon.stub() + }, + stderr: { + on: sinon.stub() + }, + on: function(event, cb) { + cb(1); + } + }) + } + }); + + createtest({ + cwd: testDir, + dirname: 'mynode4', + name: 'My Node 4', + isGlobal: false, + datadir: '../.bitcoin' + }, function(err) { + should.exist(err); + err.message.should.equal('There was an error installing dependencies.'); + }); + + }); + }); From 4f37c3f15f113ade7e950b79c5d6403d251e62e1 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 25 Aug 2015 13:22:55 -0400 Subject: [PATCH 14/17] Include cli command "call" in another iteration. --- README.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/README.md b/README.md index 7a89774d..f3d8c176 100644 --- a/README.md +++ b/README.md @@ -27,19 +27,6 @@ bitcore-node add https://github.com/yourname/helloworld This will create a directory with configuration files for your node and install the necessary dependencies. Please see the [directory](doc/modules.md) for a partial list of modules for Bitcore Node. If you're interested in developing a module, please see the [Module Development Guide](doc/modules-development.md). -## Using your Node - -Your node should be able to respond to commands via a command line utility. For example, with the address module added, you should be able to query for unspent outputs for any address: - -```bash -bitcore-node call getunspentoutputs "1HTxCVrXuthad6YW5895K98XmVsdMvvBSw" -bitcore-node call getbalance "1HTxCVrXuthad6YW5895K98XmVsdMvvBSw" -bitcore-node call sendtransaction "" -bitcore-node call help -``` - -The above are a few common examples of commands that can be run, however there are many more. Every module that is added can extend the interface to include new commands. Running `bitcore-node call help` will list all of the available commands that can be run against your node. - ## Build & Install This includes a detailed instructions for compiling. There are two main parts of the build, compiling Bitcoin Core as a static library and the Node.js bindings. From 81b2d2f2a33546a1a832368c35e1a63cdd64e2b6 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 25 Aug 2015 13:57:54 -0400 Subject: [PATCH 15/17] Added tests for start command. --- lib/scaffold/start.js | 5 ++++ test/scaffold/start.integration.js | 41 +++++++++++++++++++----------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/scaffold/start.js b/lib/scaffold/start.js index b827aedc..4ae6ea6d 100644 --- a/lib/scaffold/start.js +++ b/lib/scaffold/start.js @@ -41,6 +41,9 @@ function start(options) { // expand to the full path fullConfig.datadir = path.resolve(configPath, config.datadir); + // delete until modules move to the node + delete fullConfig.modules; + // load the modules fullConfig.db = { modules: modules @@ -208,6 +211,8 @@ function start(options) { //catches ctrl+c event process.on('SIGINT', exitHandler.bind(null, {sigint:true})); + return node; + } module.exports = start; diff --git a/test/scaffold/start.integration.js b/test/scaffold/start.integration.js index 8b7ec2bf..07daa336 100644 --- a/test/scaffold/start.integration.js +++ b/test/scaffold/start.integration.js @@ -2,27 +2,38 @@ var should = require('chai').should(); var sinon = require('sinon'); +var proxyquire = require('proxyquire'); +var AddressModule = require('../../lib/modules/address'); describe('#start', function() { - before(function() { - // setup testing directories - }); - - after(function() { - // cleanup testing directories - }); - describe('will dynamically create a node from a configuration', function() { - it('require each bitcore-node module', function() { + it('require each bitcore-node module', function(done) { + var node; + var TestNode = function(options) { + options.db.modules.should.deep.equal([AddressModule]); + }; + TestNode.prototype.on = sinon.stub(); + TestNode.prototype.chain = { + on: sinon.stub() + }; + var starttest = proxyquire('../../lib/scaffold/start', { + '../node': TestNode + }); + + node = starttest({ + path: __dirname, + config: { + modules: [ + 'address' + ], + datadir: './data' + } + }); + node.should.be.instanceof(TestNode); + done(); }); - - it('create an instance of node with modules enabled', function() { - - }); - }); - }); From 35405c9329badf1f1f5005a79ab314206f151cf8 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 25 Aug 2015 14:00:59 -0400 Subject: [PATCH 16/17] Include `lastBuild` in release process notes. --- RELEASE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 4e912af1..0df761c4 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -32,7 +32,7 @@ Ensure you've followed the instructions in the README.md for building the projec When publishing to npm, the .gitignore file is used to exclude files from the npm publishing process. Be sure that the bitcore-node directory has only the directories and files that you would like to publish to npm. You might need to run the commands below on each platform that you intend to publish (e.g. Mac and Linux). -To make a release, bump the version of the package.json: +To make a release, bump the `version` and `lastBuild` of the `package.json`: ```bash git checkout master @@ -44,7 +44,7 @@ npm run upload npm publish ``` -And then update the version of the package.json for development (e.g. "0.3.2-dev"): +And then update the `version` of the `package.json` for development (e.g. "0.3.2-dev"): ```bash git commit -a -m "Bump development version to " From 7559a4ffaa44185aeceed1f2d19db69534fdfa6d Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 25 Aug 2015 14:02:28 -0400 Subject: [PATCH 17/17] Update README with the correct links. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f3d8c176..e1b0b1e6 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ bitcore-node add bitcore-node add https://github.com/yourname/helloworld ``` -This will create a directory with configuration files for your node and install the necessary dependencies. Please see the [directory](doc/modules.md) for a partial list of modules for Bitcore Node. If you're interested in developing a module, please see the [Module Development Guide](doc/modules-development.md). +This will create a directory with configuration files for your node and install the necessary dependencies. If you're interested in developing a module, please see the [Module Development Guide](#modules). ## Build & Install