diff --git a/.travis.yml b/.travis.yml index f66abf3a..d050d692 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,15 @@ language: node_js +env: +- BITCOINDJS_ENV=test node_js: - "0.12" before_install: - sudo apt-get install libboost1.48-all-dev + - sudo add-apt-repository -y ppa:bitcoin/bitcoin + - sudo apt-get update + - sudo apt-get install libdb4.8-dev libdb4.8++-dev - git config --global user.email "dev@bitpay.com" - git config --global user.name "BitPay, Inc." +script: + - _mocha -R spec integration/regtest.js + - _mocha -R spec --recursive diff --git a/README.md b/README.md index 8dbc5d61..a76252e8 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ var BitcoinNode = require('bitcoind.js'); var configuration = { datadir: '~/.bitcoin', - testnet: true + network: 'testnet' }; var node = new BitcoinNode(configuration); @@ -110,7 +110,7 @@ $ tail -f ~/.bitcoin/debug.log ## Building -There are two main parts of the build, compiling Bitcoin Core and the Node.js bindings. You can run both by using `npm install` and `npm run debug_install`. +There are two main parts of the build, compiling Bitcoin Core and the Node.js bindings. You can run both by using `npm install` and set environment variable, $BITCOINDJS_ENV to 'test' or 'debug'. Both 'test' and 'debug' build libbitcoind with debug symbols whereas 'test' adds wallet capability so that regtest can be used. ### Node.js Bindings @@ -130,6 +130,11 @@ To be able to debug you'll need to have `gdb` and `node` compiled for debugging $ gdb --args node_g path/to/example.js ``` +To run mocha from within gdb (notice `_mocha` and not `mocha` so that the tests run in the same process): +```bash +$ gdb --args node /path/to/_mocha -R spec integration/index.js +``` + To run integration tests against testnet or livenet data: ```bash @@ -161,7 +166,7 @@ Most of all the dependencies for building Bitcoin Core are needed, for more info #### Shared Library Patch -To provide native bindings to JavaScript *(or any other language for that matter)*, Bitcoin code, itself, must be linkable. Currently, Bitcoin Core provides a JSON RPC interface to bitcoind as well as a shared library for script validation *(and hopefully more)* called libbitcoinconsensus. There is a node module, [node-libbitcoinconsensus](https://github.com/bitpay/node-libbitcoinconsensus), that exposes these methods. While these interfaces are useful for several use cases, there are additional use cases that are not fulfilled, and being able to implement customized interfaces is necessary. To be able to do this a few simple changes need to be made to Bitcoin Core to compile as a shared library. +To provide native bindings to JavaScript *(or any other language for that matter)*, Bitcoin code, itself, must be linkable. Currently, Bitcoin Core provides a JSON RPC interface to bitcoind as well as a shared library for script validation *(and hopefully more)* called libbitcoinconsensus. There is a node module, [node-libbitcoinconsensus](https://github.com/bitpay/node-libbitcoinconsensus), that exposes these methods. While these interfaces are useful for several use cases, there are additional use cases that are not fulfilled, and being able to implement customized interfaces is necessary. To be able to do this a few simple changes need to be made to Bitcoin Core to compile as a shared library. The patch is located at `etc/bitcoin.patch` and adds a configure option `--enable-daemonlib` to compile all object files with `-fPIC` (Position Independent Code - needed to create a shared object), exposes leveldb variables and objects, exposes the threadpool to the bindings, and conditionally includes the main function. @@ -176,7 +181,7 @@ $ cd /path/to/bitcoind.js $ ./bin/build-libbitcoind ``` -The first argument is 'debug', this will compile node bindings and bitcoind with debug flags. The `PATCH_VERSION` file dictates what version/tag the patch goes clean against. +The `PATCH_VERSION` file dictates what version/tag the patch goes clean against. There is a config_options.sh that has the configure options used to build libbitcoind. `make` will then compile `libbitcoind/src/.libs/libbitcoind.{so|dylib}`. This will completely ignore compiling tests, QT object files and the wallet features in `bitcoind/libbitcoind.{so|dylib}`. diff --git a/benchmarks/index.js b/benchmarks/index.js index 574d512c..436ccabf 100644 --- a/benchmarks/index.js +++ b/benchmarks/index.js @@ -28,7 +28,7 @@ var fixtureData = { var bitcoind = require('../').daemon({ datadir: process.env.BITCOINDJS_DIR || '~/.bitcoin', - testnet: true + network: 'testnet' }); bitcoind.on('error', function(err) { diff --git a/bin/build-libbitcoind b/bin/build-libbitcoind index b73d2d75..dbd1dcf9 100755 --- a/bin/build-libbitcoind +++ b/bin/build-libbitcoind @@ -3,6 +3,7 @@ set -e root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.." cd "${root_dir}" +options=`cat ${root_dir}/bin/config_options.sh` os_dir=$(./platform/os.sh osdir) @@ -10,8 +11,13 @@ os_dir=$(./platform/os.sh osdir) export LD_LIBRARY_PATH="${root_dir}/libbitcoind/src/leveldb":"${os_dir}":$LD_LIBRARY_PATH debug= -if test x"$1" = x'debug'; then - debug=--enable-debug +if [ "${BITCOINDJS_ENV}" == "debug" ]; then + options=`cat ${root_dir}/bin/config_options_debug.sh` +fi + +test= +if [ "${BITCOINDJS_ENV}" == "test" ]; then + options=`cat ${root_dir}/bin/config_options_test.sh` fi btc_dir="${root_dir}/libbitcoind" @@ -50,12 +56,11 @@ if [ "${only_make}" = false ]; then echo './autogen.sh' ./autogen.sh - options=`cat ${root_dir}/bin/config_options.sh` - full_options="${options}${os_dir} ${debug}" - echo "running the configure script with the following options:\n :::[\"${full_options}\"]:::" - ${full_options} fi +full_options="${options}${os_dir}" +echo "running the configure script with the following options:\n :::[\"${full_options}\"]:::" +${full_options} echo 'make V=1' make V=1 diff --git a/bin/config_options_debug.sh b/bin/config_options_debug.sh new file mode 100644 index 00000000..afacbc9f --- /dev/null +++ b/bin/config_options_debug.sh @@ -0,0 +1,2 @@ +./configure --enable-debug --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --without-bdb --disable-wallet --without-utils --prefix= + diff --git a/bin/config_options_test.sh b/bin/config_options_test.sh new file mode 100644 index 00000000..225f7aa0 --- /dev/null +++ b/bin/config_options_test.sh @@ -0,0 +1,2 @@ +./configure --enable-debug --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --prefix= + diff --git a/etc/bitcoin.patch b/etc/bitcoin.patch index 5606ee64..c71c258f 100644 --- a/etc/bitcoin.patch +++ b/etc/bitcoin.patch @@ -1,18 +1,8 @@ -From 05084f2a640b862132588b322461ec8e13058fc3 Mon Sep 17 00:00:00 2001 -From: Chris Kleeschulte -Date: Mon, 13 Jul 2015 12:49:30 -0400 -Subject: [PATCH] libbitcoind +commit 29c1ca452ba6178d6b17be0a0b5a65567ba846af +Author: Chris Kleeschulte +Date: Mon Jul 13 16:35:37 2015 -0400 ---- - config_me.sh | 1 + - configure.ac | 37 ++++++++++++++++++++++++++++++++----- - src/Makefile.am | 42 ++++++++++++++++++++++++++++++++++-------- - src/bitcoind.cpp | 6 ++++++ - src/init.h | 5 +++++ - src/leveldb/Makefile | 6 +++++- - src/leveldbwrapper.h | 12 ++++++++++++ - 7 files changed, 95 insertions(+), 14 deletions(-) - create mode 100644 config_me.sh + allow compiling of libbitcoind.so. diff --git a/config_me.sh b/config_me.sh new file mode 100644 @@ -22,13 +12,13 @@ index 0000000..19e9a1b @@ -0,0 +1 @@ +./configure --enable-tests=no --enable-daemonlib --with-gui=no --without-qt --without-miniupnpc --without-bdb --enable-debug --disable-wallet --without-utils diff --git a/configure.ac b/configure.ac -index 37fe47e..27a9b9a 100644 +index 37fe47e..83cfe70 100644 --- a/configure.ac +++ b/configure.ac @@ -119,6 +119,12 @@ AC_ARG_ENABLE([reduce-exports], [use_reduce_exports=$enableval], [use_reduce_exports=no]) - + +AC_ARG_ENABLE([daemonlib], + [AS_HELP_STRING([--enable-daemonlib], + [compile all of bitcoind as a library (default is no)])], @@ -45,13 +35,13 @@ index 37fe47e..27a9b9a 100644 + if test x$use_daemonlib = xno; then + AX_CHECK_COMPILE_FLAG([-fPIE],[HARDENED_CXXFLAGS="$HARDENED_CXXFLAGS -fPIE"]) + fi - + AX_CHECK_PREPROC_FLAG([-D_FORTIFY_SOURCE=2],[ AX_CHECK_PREPROC_FLAG([-U_FORTIFY_SOURCE],[ @@ -415,7 +424,7 @@ if test x$use_hardening != xno; then AX_CHECK_LINK_FLAG([[-Wl,-z,relro]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-z,relro"]) AX_CHECK_LINK_FLAG([[-Wl,-z,now]], [HARDENED_LDFLAGS="$HARDENED_LDFLAGS -Wl,-z,now"]) - + - if test x$TARGET_OS != xwindows; then + if test x$TARGET_OS != xwindows -a x$use_daemonlib = xno; then # All windows code is PIC, forcing it on just adds useless compile warnings @@ -60,7 +50,7 @@ index 37fe47e..27a9b9a 100644 @@ -433,6 +442,17 @@ if test x$use_hardening != xno; then OBJCXXFLAGS="$CXXFLAGS" fi - + +AC_DEFINE([ENABLE_DAEMONLIB],[0],[Enable daemonlib.]) +AM_CONDITIONAL([ENABLE_DAEMONLIB],[false]) +if test x$use_daemonlib != xno; then @@ -78,13 +68,13 @@ index 37fe47e..27a9b9a 100644 @@ -483,11 +503,18 @@ AC_LINK_IFELSE([AC_LANG_SOURCE([ ] ) - + -if test x$use_reduce_exports = xyes; then +if test x$use_reduce_exports = xyes -a x$use_daemonlib = xno; then AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],[RE_CXXFLAGS="-fvisibility=hidden"], [AC_MSG_ERROR([Cannot set default symbol visibility. Use --disable-reduce-exports.])]) fi - + +AC_MSG_CHECKING([whether to compile as daemonlib]) +if test x$use_daemonlib != xno; then + AC_MSG_RESULT([yes]) @@ -96,18 +86,19 @@ index 37fe47e..27a9b9a 100644 LIBLEVELDB= LIBMEMENV= diff --git a/src/Makefile.am b/src/Makefile.am -index 1c2f770..632f608 100644 +index 1c2f770..ddcae0f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am -@@ -1,6 +1,7 @@ +@@ -1,6 +1,8 @@ DIST_SUBDIRS = secp256k1 AM_LDFLAGS = $(PTHREAD_CFLAGS) $(LIBTOOL_LDFLAGS) - + +lib_LTLIBRARIES = - ++libbitcoind_la_LIBADD = + if EMBEDDED_LEVELDB LEVELDB_CPPFLAGS += -I$(srcdir)/leveldb/include -@@ -15,6 +16,10 @@ $(LIBLEVELDB) $(LIBMEMENV): +@@ -15,6 +17,10 @@ $(LIBLEVELDB) $(LIBMEMENV): @echo "Building LevelDB ..." && $(MAKE) -C $(@D) $(@F) CXX="$(CXX)" \ CC="$(CC)" PLATFORM=$(TARGET_OS) AR="$(AR)" $(LEVELDB_TARGET_FLAGS) \ OPT="$(CXXFLAGS) $(CPPFLAGS) -D__STDC_LIMIT_MACROS" @@ -116,12 +107,12 @@ index 1c2f770..632f608 100644 + @echo "Building the LevelDB shared library..." && $(MAKE) -C ./leveldb + endif - + BITCOIN_CONFIG_INCLUDES=-I$(builddir)/config -@@ -49,16 +54,16 @@ BITCOIN_INCLUDES += $(BDB_CPPFLAGS) +@@ -49,16 +55,16 @@ BITCOIN_INCLUDES += $(BDB_CPPFLAGS) EXTRA_LIBRARIES += libbitcoin_wallet.a endif - + -if BUILD_BITCOIN_LIBS -lib_LTLIBRARIES = libbitcoinconsensus.la -LIBBITCOIN_CONSENSUS=libbitcoinconsensus.la @@ -132,7 +123,7 @@ index 1c2f770..632f608 100644 +LIBBITCOIN_CONSENSUS = bin_PROGRAMS = TESTS = - + +if BUILD_BITCOIN_LIBS +lib_LTLIBRARIES += libbitcoinconsensus.la +LIBBITCOIN_CONSENSUS += libbitcoinconsensus.la @@ -142,28 +133,28 @@ index 1c2f770..632f608 100644 if BUILD_BITCOIND bin_PROGRAMS += bitcoind endif -@@ -66,6 +71,9 @@ endif +@@ -66,6 +72,9 @@ endif if BUILD_BITCOIN_UTILS bin_PROGRAMS += bitcoin-cli bitcoin-tx endif +else +lib_LTLIBRARIES += libbitcoind.la +endif - + .PHONY: FORCE # bitcoin core # -@@ -169,8 +177,9 @@ obj/build.h: FORCE +@@ -169,8 +178,9 @@ obj/build.h: FORCE @$(MKDIR_P) $(builddir)/obj @$(top_srcdir)/share/genbuild.sh $(abs_top_builddir)/src/obj/build.h \ $(abs_top_srcdir) -libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h - + +libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h +clientversion.cpp: obj/build.h # server: shared between bitcoind and bitcoin-qt libbitcoin_server_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) libbitcoin_server_a_SOURCES = \ -@@ -309,9 +318,18 @@ nodist_libbitcoin_util_a_SOURCES = $(srcdir)/obj/build.h +@@ -309,9 +319,18 @@ nodist_libbitcoin_util_a_SOURCES = $(srcdir)/obj/build.h bitcoind_SOURCES = bitcoind.cpp bitcoind_CPPFLAGS = $(BITCOIN_INCLUDES) bitcoind_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) @@ -175,37 +166,41 @@ index 1c2f770..632f608 100644 +libbitcoind_la_SOURCES += $(libbitcoin_server_a_SOURCES) +libbitcoind_la_SOURCES += $(crypto_libbitcoin_crypto_a_SOURCES) +libbitcoind_la_SOURCES += $(univalue_libbitcoin_univalue_a_SOURCES) - + if TARGET_WINDOWS bitcoind_SOURCES += bitcoind-res.rc +libbitcoind_la_SOURCES += bitcoind-res.rc endif - + bitcoind_LDADD = \ -@@ -328,7 +346,15 @@ if ENABLE_WALLET +@@ -326,9 +345,19 @@ bitcoind_LDADD = \ + + if ENABLE_WALLET bitcoind_LDADD += libbitcoin_wallet.a ++libbitcoind_la_LIBADD += $(BDB_LIBS) ++libbitcoind_la_SOURCES += $(libbitcoin_wallet_a_SOURCES) endif - + +MEMOBJ = helpers/memenv/memenv.lo +$(MEMOBJ): + @echo "Building the Memenv shared library..." && $(MAKE) -C ./leveldb $@ + bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) -+libbitcoind_la_LIBADD = $(BOOST_LIBS) $(SSL_LIBS) $(LIBSECP256K1) $(CRYPTO_LIBS) leveldb/$(MEMOBJ) ++libbitcoind_la_LIBADD += $(BOOST_LIBS) $(SSL_LIBS) $(LIBSECP256K1) $(CRYPTO_LIBS) leveldb/$(MEMOBJ) +libbitcoind_la_CPPFLAGS = $(BITCOIN_INCLUDES) +libbitcoind_la_LDFLAGS = -lleveldb -L./leveldb $(RELDFLAGS) -no-undefined +libbitcoind_la_DEPENDENCIES = $(LIBSECP256K1) LIBLEVELDB_SHARED $(MEMOBJ) # - + # bitcoin-cli binary # diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index cce687a..0f162ff 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -33,6 +33,10 @@ - + static bool fDaemon; - + +#if ENABLE_DAEMONLIB +extern void WaitForShutdown(boost::thread_group* threadGroup); +#endif @@ -216,13 +211,13 @@ index cce687a..0f162ff 100644 @@ -166,6 +170,7 @@ bool AppInit(int argc, char* argv[]) return fRet; } - + +#if !ENABLE_DAEMONLIB int main(int argc, char* argv[]) { SetupEnvironment(); @@ -175,3 +180,4 @@ int main(int argc, char* argv[]) - + return (AppInit(argc, argv) ? 0 : 1); } +#endif @@ -231,9 +226,9 @@ index dcb2b29..5ce68ba 100644 --- a/src/init.h +++ b/src/init.h @@ -18,6 +18,11 @@ class thread_group; - + extern CWallet* pwalletMain; - + +#if ENABLE_DAEMONLIB +#include +#include @@ -248,24 +243,24 @@ index 2bd2cad..490ba66 100644 +++ b/src/leveldb/Makefile @@ -103,7 +103,7 @@ check: all $(PROGRAMS) $(TESTS) for t in $(TESTS); do echo "***** Running $$t"; ./$$t || exit 1; done - + clean: - -rm -f $(PROGRAMS) $(BENCHMARKS) $(LIBRARY) $(SHARED) $(MEMENVLIBRARY) */*.o */*/*.o ios-x86/*/*.o ios-arm/*/*.o build_config.mk + -rm -f $(PROGRAMS) $(BENCHMARKS) $(LIBRARY) $(SHARED) $(MEMENVLIBRARY) */*.o */*/*.lo helpers/memenv/.deps/*.Plo helpers/memenv/.deps/*.Tpo */*/*.o ios-x86/*/*.o ios-arm/*/*.o build_config.mk -rm -rf ios-x86/* ios-arm/* - + $(LIBRARY): $(LIBOBJECTS) @@ -192,6 +192,10 @@ $(MEMENVLIBRARY) : $(MEMENVOBJECTS) rm -f $@ $(AR) -rs $@ $(MEMENVOBJECTS) - + +helpers/memenv/memenv.lo: helpers/memenv/memenv.cc + -mkdir -p helpers/memenv/.deps + /bin/bash ../../libtool --tag=CXX --mode=compile $(CXX) $(CXXFLAGS) $(CFLAGS) -fPIC -MT $@ -MD -MP -MF helpers/memenv/.deps/memenv.Tpo -c -o $@ $< + memenv_test : helpers/memenv/memenv_test.o $(MEMENVLIBRARY) $(LIBRARY) $(TESTHARNESS) $(CXX) $(LDFLAGS) helpers/memenv/memenv_test.o $(MEMENVLIBRARY) $(LIBRARY) $(TESTHARNESS) -o $@ $(LIBS) - + diff --git a/src/leveldbwrapper.h b/src/leveldbwrapper.h index c65e842..0e44bb5 100644 --- a/src/leveldbwrapper.h @@ -273,14 +268,14 @@ index c65e842..0e44bb5 100644 @@ -29,10 +29,16 @@ class CLevelDBBatch { friend class CLevelDBWrapper; - + +#if ENABLE_DAEMONLIB +public: +#else private: +#endif leveldb::WriteBatch batch; - + +#if !ENABLE_DAEMONLIB public: +#endif @@ -288,7 +283,7 @@ index c65e842..0e44bb5 100644 void Write(const K& key, const V& value) { @@ -63,7 +69,11 @@ public: - + class CLevelDBWrapper { +#if ENABLE_DAEMONLIB @@ -298,17 +293,14 @@ index c65e842..0e44bb5 100644 +#endif //! custom environment this database is using (may be NULL in case of default environment) leveldb::Env* penv; - + @@ -85,7 +95,9 @@ private: //! the database itself leveldb::DB* pdb; - + +#if !ENABLE_DAEMONLIB public: +#endif CLevelDBWrapper(const boost::filesystem::path& path, size_t nCacheSize, bool fMemory = false, bool fWipe = false); ~CLevelDBWrapper(); - --- -2.3.2 (Apple Git-55) - + diff --git a/example/getblock.js b/example/getblock.js deleted file mode 100644 index 06a96fff..00000000 --- a/example/getblock.js +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env node - -/** - * bitcoind.js example - */ - -process.title = 'bitcoind.js'; - -/** - * daemon - */ - -var daemon = require('../index.js').daemon({ - directory: process.env.BITCOINDJS_DIR || '~/.bitcoin' -}); - -daemon.on('error', function(err) { - daemon.log('error="%s"', err.message); -}); - -daemon.on('ready', function(err, result) { - console.log('Ready!'); - - daemon.getBlock('000000000000000082ccf8f1557c5d40b21edabb18d2d691cfbf87118bac7254', function(err, block) { - if (err) { - console.log(err); - } - console.log('block', block); - }); - -}); - -daemon.on('open', function(status) { - daemon.log('status="%s"', status); -}); diff --git a/example/index.js b/example/index.js index f910357a..98e5c7b4 100755 --- a/example/index.js +++ b/example/index.js @@ -13,7 +13,8 @@ process.title = 'bitcoind.js'; */ var daemon = require('../').daemon({ - directory: process.env.BITCOINDJS_DIR || '~/.bitcoin' + datadir: process.env.BITCOINDJS_DIR || '~/.bitcoin', + network: 'regtest' }); daemon.on('error', function(err) { diff --git a/example/index_stripped.js b/example/index_stripped.js deleted file mode 100755 index b45d5e6b..00000000 --- a/example/index_stripped.js +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env node - -/** - * bitcoind.js example - */ - -process.title = 'bitcoind_stripped.js'; - -/** - * daemon - */ - -var daemon = require('../index_stripped.js')({ - directory: '~/.libbitcoind-example' -}); - -daemon.on('error', function(err) { - daemon.log('error="%s"', err.message); -}); - -daemon.on('open', function(status) { - daemon.log('status="%s"', status); -}); diff --git a/example/node.js b/example/node.js index 7d50950d..3ab2d5a4 100644 --- a/example/node.js +++ b/example/node.js @@ -8,7 +8,7 @@ var log = chainlib.log; var configuration = { datadir: process.env.BITCOINDJS_DIR || '~/.bitcoin', - testnet: true + network: 'testnet' }; var node = new BitcoinNode(configuration); diff --git a/integration/data/bitcoin.conf b/integration/data/bitcoin.conf new file mode 100644 index 00000000..2ebd94ba --- /dev/null +++ b/integration/data/bitcoin.conf @@ -0,0 +1,8 @@ +server=1 +whitelist=127.0.0.1 +txindex=1 +rpcallowip=127.0.0.1 +rpcuser=bitcoin +rpcpassword=local321 + + diff --git a/integration/index.js b/integration/livenet.js similarity index 98% rename from integration/index.js rename to integration/livenet.js index 94c9024c..245b19d5 100644 --- a/integration/index.js +++ b/integration/livenet.js @@ -1,7 +1,7 @@ 'use strict'; // These tests require a fully synced Bitcore Code data directory. -// To run the tests: $ mocha -R spec index.js +// To run the tests: $ mocha -R spec livenet.js var chai = require('chai'); var bitcore = require('bitcore'); @@ -23,7 +23,7 @@ describe('Basic Functionality', function() { before(function(done) { this.timeout(30000); bitcoind = require('../').daemon({ - directory: process.env.BITCOINDJS_DIR || '~/.bitcoin', + datadir: process.env.BITCOINDJS_DIR || '~/.bitcoin', }); bitcoind.on('error', function(err) { diff --git a/integration/regtest.js b/integration/regtest.js new file mode 100644 index 00000000..bc313a3b --- /dev/null +++ b/integration/regtest.js @@ -0,0 +1,172 @@ +'use strict'; + +// These tests require bitcoind.js Bitcoin Core bindings to be compiled with +// the environment variable BITCOINDJS_ENV=test. This enables the use of regtest +// functionality by including the wallet in the build. +// To run the tests: $ mocha -R spec integration/regtest.js + +if (process.env.BITCOINDJS_ENV !== 'test') { + console.log('Please set the environment variable BITCOINDJS_ENV=test and make sure bindings are compiled for testing'); + process.exit(); +} + +var chai = require('chai'); +var bitcore = require('bitcore'); +var rimraf = require('rimraf'); +var bitcoind; + +/* jshint unused: false */ +var should = chai.should(); +var assert = chai.assert; +var sinon = require('sinon'); +var BitcoinRPC = require('bitcoind-rpc'); +var blockHashes = []; + +describe('Basic Functionality', function() { + + before(function(done) { + this.timeout(30000); + + var datadir = __dirname + '/data'; + + rimraf(datadir + '/regtest', function(err) { + + if (err) { + throw err; + } + + bitcoind = require('../').daemon({ + datadir: datadir, + network: 'regtest' + }); + + bitcoind.on('error', function(err) { + bitcoind.log('error="%s"', err.message); + }); + + bitcoind.on('open', function(status) { + bitcoind.log('status="%s"', status); + }); + + console.log('Waiting for Bitcoin Core to initialize...'); + + bitcoind.on('ready', function() { + + var client = new BitcoinRPC({ + protocol: 'http', + host: '127.0.0.1', + port: 18332, + user: 'bitcoin', + pass: 'local321' + }); + + console.log('Generating 100 blocks...'); + + client.generate(100, function(err, response) { + if (err) { + throw err; + } + blockHashes = response.result; + done(); + }); + + }); + + }); + + }); + + after(function(done) { + this.timeout(20000); + bitcoind.stop(function(err, result) { + done(); + }); + }); + + describe('mempool functionality', function() { + + var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1'; + var utxo = { + address: fromAddress, + txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458', + outputIndex: 0, + script: bitcore.Script.buildPublicKeyHashOut(fromAddress).toString(), + satoshis: 100000 + }; + var toAddress = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc'; + var changeAddress = 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up'; + var changeAddressP2SH = '2N7T3TAetJrSCruQ39aNrJvYLhG1LJosujf'; + var privateKey = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY'; + var private1 = '6ce7e97e317d2af16c33db0b9270ec047a91bff3eff8558afb5014afb2bb5976'; + var private2 = 'c9b26b0f771a0d2dad88a44de90f05f416b3b385ff1d989343005546a0032890'; + var tx = new bitcore.Transaction(); + tx.from(utxo); + tx.to(toAddress, 50000); + tx.change(changeAddress); + tx.sign(privateKey); + + it('will add an unchecked transaction', function() { + var added = bitcoind.addMempoolUncheckedTransaction(tx.serialize()); + added.should.equal(true); + bitcoind.getTransaction(tx.hash, true, function(err, txBuffer) { + if(err) { + throw err; + } + var expected = tx.toBuffer().toString('hex'); + txBuffer.toString('hex').should.equal(expected); + }); + + }); + + it('get outputs by address', function() { + var outputs = bitcoind.getMempoolOutputs(changeAddress); + var expected = [ + { + script: 'OP_DUP OP_HASH160 073b7eae2823efa349e3b9155b8a735526463a0f OP_EQUALVERIFY OP_CHECKSIG', + satoshis: 40000, + txid: tx.hash, + outputIndex: 1 + } + ]; + outputs.should.deep.equal(expected); + }); + + }); + + describe('get blocks by hash', function() { + + [0,1,2,3,5,6,7,8,9].forEach(function(i) { + it('generated block ' + i, function(done) { + bitcoind.getBlock(blockHashes[i], function(err, response) { + if (err) { + throw err; + } + should.exist(response); + var block = bitcore.Block.fromBuffer(response); + block.hash.should.equal(blockHashes[i]); + done(); + }); + }); + }); + }); + + describe('get blocks by height', function() { + + [0,1,2,3,5,6,7,8,9].forEach(function(i) { + it('generated block ' + i, function(done) { + // add the genesis block + var height = i + 1; + bitcoind.getBlock(i + 1, function(err, response) { + if (err) { + throw err; + } + should.exist(response); + var block = bitcore.Block.fromBuffer(response); + block.hash.should.equal(blockHashes[i]); + done(); + }); + }); + }); + }); + +}); diff --git a/lib/daemon.js b/lib/daemon.js index 5295c925..4e529ebc 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -37,14 +37,6 @@ function Daemon(options) { this.options = options || {}; - if (typeof this.options === 'string') { - this.options = { datadir: this.options }; - } - - if (this.options.directory) { - this.options.datadir = this.options.directory; - delete this.options.directory; - } if (!this.options.datadir) { this.options.datadir = '~/.bitcoind.js'; @@ -54,7 +46,13 @@ function Daemon(options) { this.datadir = this.options.datadir; this.config = this.datadir + '/bitcoin.conf'; - this.network = Daemon[this.options.testnet ? 'testnet' : 'livenet']; + this.network = Daemon['livenet']; + + if (this.options.network === 'testnet') { + this.network = Daemon['testnet']; + } else if(this.options.network === 'regtest') { + this.network = Daemon['regtest']; + } if (!fs.existsSync(this.datadir)) { mkdirp.sync(this.datadir); @@ -83,7 +81,6 @@ function Daemon(options) { fs.writeFileSync(data + peers); } - // Copy config into testnet dir if (this.network.name === 'testnet') { if (!fs.existsSync(this.datadir + '/testnet3')) { fs.mkdirSync(this.datadir + '/testnet3'); @@ -93,6 +90,15 @@ function Daemon(options) { fs.readFileSync(this.config)); } + if (this.network.name === 'regtest') { + if (!fs.existsSync(this.datadir + '/regtest')) { + fs.mkdirSync(this.datadir + '/regtest'); + } + fs.writeFileSync( + this.datadir + '/regtest/bitcoin.conf', + fs.readFileSync(this.config)); + } + Object.keys(exports).forEach(function(key) { self[key] = exports[key]; }); @@ -120,6 +126,13 @@ Daemon.testnet = { ] }; +Daemon.regtest = { + name: 'regtest', + peers: [ + // hardcoded peers + ] +}; + // Make sure signal handlers are not overwritten Daemon._signalQueue = []; Daemon._processOn = process.on; diff --git a/lib/daemon_stripped.js b/lib/daemon_stripped.js deleted file mode 100644 index 1783389c..00000000 --- a/lib/daemon_stripped.js +++ /dev/null @@ -1,959 +0,0 @@ -/** - * bitcoind.js - * Copyright (c) 2014, BitPay (MIT License) - * A bitcoind node.js binding. - */ - -var net = require('net'); -var EventEmitter = require('events').EventEmitter; -var bitcoindjs = require('../build/Debug/bitcoindjs.node'); -var util = require('util'); -var fs = require('fs'); -var mkdirp = require('mkdirp'); -var tiny = require('tiny').json; - -// Compatibility with old node versions: -var setImmediate = global.setImmediate || process.nextTick.bind(process); - -/** - * Daemon - */ - -var daemon = Daemon; - -function Daemon(options) { - var self = this; - - if (!(this instanceof Daemon)) { - return new Daemon(options); - } - - if (Object.keys(this.instances).length) { - throw new - Error('bitcoind.js cannot be instantiated more than once.'); - } - - EventEmitter.call(this); - - this.options = options || {}; - - if (typeof this.options === 'string') { - this.options = { datadir: this.options }; - } - - if (this.options.directory) { - this.options.datadir = this.options.directory; - delete this.options.directory; - } - - if (!this.options.datadir) { - this.options.datadir = '~/.bitcoind.js'; - } - - this.options.datadir = this.options.datadir.replace(/^~/, process.env.HOME); - - this.datadir = this.options.datadir; - this.config = this.datadir + '/bitcoin.conf'; - this.network = Daemon[this.options.testnet ? 'testnet' : 'livenet']; - - if (!fs.existsSync(this.datadir)) { - mkdirp.sync(this.datadir); - } - - if (!fs.existsSync(this.config)) { - var password = '' - + Math.random().toString(36).slice(2) - + Math.random().toString(36).slice(2) - + Math.random().toString(36).slice(2); - fs.writeFileSync(this.config, '' - + 'rpcuser=bitcoinrpc\n' - + 'rpcpassword=' + password + '\n' - ); - } - - // Add hardcoded peers - var data = fs.readFileSync(this.config, 'utf8'); - if (this.network.peers.length) { - var peers = this.network.peers.reduce(function(out, peer) { - if (!~data.indexOf('addnode=' + peer)) { - return out + 'addnode=' + peer + '\n'; - } - return out; - }, '\n'); - fs.writeFileSync(data + peers); - } - - // Copy config into testnet dir - if (this.network.name === 'testnet') { - if (!fs.existsSync(this.datadir + '/testnet3')) { - fs.mkdirSync(this.datadir + '/testnet3'); - } - fs.writeFileSync( - this.datadir + '/testnet3/bitcoin.conf', - fs.readFileSync(this.config)); - } - - Object.keys(exports).forEach(function(key) { - self[key] = exports[key]; - }); - - this.on('newListener', function(name) { - if (name === 'open') { - self.start(); - } - }); -} - -Daemon.prototype.__proto__ = EventEmitter.prototype; - -Daemon.livenet = { - name: 'livenet', - peers: [ - // hardcoded peers - ] -}; - -Daemon.testnet = { - name: 'testnet', - peers: [ - // hardcoded peers - ] -}; - -// Make sure signal handlers are not overwritten -Daemon._signalQueue = []; -Daemon._processOn = process.on; -process.addListener = -process.on = function(name, listener) { - if (~['SIGINT', 'SIGHUP', 'SIGQUIT'].indexOf(name.toUpperCase())) { - if (!Daemon.global || !Daemon.global._started) { - Daemon._signalQueue.push([name, listener]); - return; - } - } - return Daemon._processOn.apply(this, arguments); -}; - -Daemon.instances = {}; -Daemon.prototype.instances = Daemon.instances; - -Daemon.__defineGetter__('global', function() { - if (daemon.stopping) return []; - return Daemon.instances[Object.keys(Daemon.instances)[0]]; -}); - -Daemon.prototype.__defineGetter__('global', function() { - if (daemon.stopping) return []; - return Daemon.global; -}); - -tiny.debug = function() {}; -tiny.prototype.debug = function() {}; -tiny.error = function() {}; -tiny.prototype.error = function() {}; - -Daemon.db = tiny({ - file: process.env.HOME + '/.bitcoindjs.db', - saveIndex: false, - initialCache: false -}); - -Daemon.prototype.start = function(options, callback) { - var self = this; - - if (!callback) { - callback = options; - options = null; - } - - if (!options) { - options = {}; - } - - if (!callback) { - callback = utils.NOOP; - } - - if (this.instances[this.datadir]) { - return; - } - this.instances[this.datadir] = true; - - var none = {}; - var isSignal = {}; - var sigint = { name: 'SIGINT', signal: isSignal }; - var sighup = { name: 'SIGHUP', signal: isSignal }; - var sigquit = { name: 'SIGQUIT', signal: isSignal }; - var exitCaught = none; - var errorCaught = none; - - Object.keys(this.options).forEach(function(key) { - if (options[key] == null) { - options[key] = self.options[key]; - } - }); - - bitcoindjs.start(options, function(err, status) { - self._started = true; - - - [sigint, sighup, sigquit].forEach(function(signal) { - process.on(signal.name, signal.listener = function() { - if (process.listeners(signal.name).length > 1) { - return; - } - if (!self._shutdown) { - process.exit(0); - } else { - self.stop(); - exitCaught = signal; - } - }); - }); - - // Finally set signal handlers - process.on = process.addListener = Daemon._processOn; - Daemon._signalQueue.forEach(function(event) { - process.on(event[0], event[1]); - }); - - var exit = process.exit; - self._exit = function() { - return exit.apply(process, arguments); - }; - - process.exit = function(code) { - exitCaught = code || 0; - if (!self._shutdown) { - return self._exit(code); - } - self.stop(); - }; - - process.on('uncaughtException', function(err) { - if (process.listeners('uncaughtException').length > 1) { - return; - } - errorCaught = err; - self.error('Uncaught error: shutting down safely before throwing...'); - if (!self._shutdown) { - if (err && err.stack) { - console.error(err.stack); - } - self._exit(1); - return; - } - self.stop(); - }); - - setTimeout(function callee() { - // Wait until wallet is loaded: - if (callback) { - callback(err ? err : null); - } - - if (err) { - self.emit('error', err); - } else { - if (callback) { - self.emit('open', status); - } else { - self.emit('status', status); - } - } - - if (callback) { - callback = null; - } - }, 100); - }); - - // bitcoind's boost threads aren't in the thread pool - // or on node's event loop, so we need to keep node open. - this._shutdown = setInterval(function() { - if (!self._stoppingSaid && bitcoindjs.stopping()) { - self._stoppingSaid = true; - self.log('shutting down...'); - } - - if (bitcoindjs.stopped()) { - self.log('shut down.'); - - clearInterval(self._shutdown); - delete self._shutdown; - - if (exitCaught !== none) { - if (exitCaught.signal === isSignal) { - process.removeListener(exitCaught.name, exitCaught.listener); - setImmediate(function() { - process.kill(process.pid, exitCaught.name); - }); - return; - } - return self._exit(exitCaught); - } - - if (errorCaught !== none) { - if (errorCaught && errorCaught.stack) { - console.error(errorCaught.stack); - } - return self._exit(0); - } - } - }, 1000); -}; - -Daemon.prototype.getBlock = function(blockhash, callback) { - if (daemon.stopping) return []; - return bitcoindjs.getBlock(blockhash, function(err, block) { - if (err) return callback(err); - return callback(null, daemon.block(block)); - }); -}; - -Daemon.prototype.getBlockHeight = function(height, callback) { - if (daemon.stopping) return []; - return bitcoindjs.getBlock(+height, function(err, block) { - if (err) return callback(err); - return callback(null, daemon.block(block)); - }); -}; - -Daemon.prototype.getTransaction = -Daemon.prototype.getTx = function(txid, blockhash, callback) { - if (daemon.stopping) return []; - if (typeof txid === 'object' && txid) { - var options = txid; - callback = blockhash; - txid = options.txid || options.tx || options.txhash || options.id || options.hash; - blockhash = options.blockhash || options.block; - } - - if (typeof blockhash === 'function') { - callback = blockhash; - blockhash = ''; - } - - if (typeof blockhash !== 'string') { - if (blockhash) { - blockhash = blockhash.hash - || blockhash.blockhash - || (blockhash.getHash && blockhash.getHash()) - || ''; - } else { - blockhash = ''; - } - } - - return bitcoindjs.getTransaction(txid, blockhash, function(err, tx) { - if (err) return callback(err); - return callback(null, daemon.tx(tx)); - }); -}; - -Daemon.prototype.getTransactionWithBlock = function(txid, blockhash, callback) { - if (daemon.stopping) return []; - - var self = this; - var slow = true; - - if (typeof txid === 'object' && txid) { - var options = txid; - callback = blockhash; - txid = options.txid || options.tx || options.txhash || options.id || options.hash; - blockhash = options.blockhash || options.block; - slow = options.slow !== false; - } - - if (typeof blockhash === 'function') { - callback = blockhash; - blockhash = ''; - } - - if (typeof blockhash !== 'string') { - if (blockhash) { - blockhash = blockhash.hash - || blockhash.blockhash - || (blockhash.getHash && blockhash.getHash()) - || ''; - } else { - blockhash = ''; - } - } - - return bitcoindjs.getTransaction(txid, blockhash, function(err, tx) { - if (err) return callback(err); - - if (slow && !tx.blockhash) { - return self.getBlockByTx(txid, function(err, block, tx_) { - if (err) return callback(err); - return callback(null, tx, block); - }); - } - - return bitcoindjs.getBlock(tx.blockhash, function(err, block) { - if (err) return callback(err); - return callback(null, daemon.tx(tx), daemon.block(block)); - }); - }); -}; - -Daemon.prototype.getInfo = function() { - if (daemon.stopping) return []; - return bitcoindjs.getInfo(); -}; - -Daemon.prototype.getPeerInfo = function() { - if (daemon.stopping) return []; - return bitcoindjs.getPeerInfo(); -}; - -Daemon.prototype.getAddresses = function() { - if (daemon.stopping) return []; - return bitcoindjs.getAddresses(); -}; - -Daemon.prototype.getProgress = function(callback) { - if (daemon.stopping) return []; - return bitcoindjs.getProgress(callback); -}; - -Daemon.prototype.setGenerate = function(options) { - if (daemon.stopping) return []; - return bitcoindjs.setGenerate(options || {}); -}; - -Daemon.prototype.getGenerate = function(options) { - if (daemon.stopping) return []; - return bitcoindjs.getGenerate(options || {}); -}; - -Daemon.prototype.getMiningInfo = function() { - if (daemon.stopping) return []; - return bitcoindjs.getMiningInfo(); -}; - -Daemon.prototype.getAddrTransactions = function(address, callback) { - if (daemon.stopping) return []; - return daemon.db.get('addr-tx/' + address, function(err, records) { - var options = { - address: address, - blockheight: (records || []).reduce(function(out, record) { - return record.blockheight > out - ? record.blockheight - : out; - }, -1), - blocktime: (records || []).reduce(function(out, record) { - return record.blocktime > out - ? record.blocktime - : out; - }, -1) - }; - return bitcoindjs.getAddrTransactions(options, function(err, addr) { - if (err) return callback(err); - addr = daemon.addr(addr); - if (addr.tx[0] && !addr.tx[0].vout[0]) { - return daemon.db.set('addr-tx/' + address, [{ - txid: null, - blockhash: null, - blockheight: null, - blocktime: null - }], function() { - return callback(null, daemon.addr({ - address: addr.address, - tx: [] - })); - }); - } - var set = []; - if (records && records.length) { - set = records; - } - addr.tx.forEach(function(tx) { - set.push({ - txid: tx.txid, - blockhash: tx.blockhash, - blockheight: tx.blockheight, - blocktime: tx.blocktime - }); - }); - return daemon.db.set('addr-tx/' + address, set, function() { - return callback(null, addr); - }); - }); - }); -}; - -Daemon.prototype.getBestBlock = function(callback) { - if (daemon.stopping) return []; - var hash = bitcoindjs.getBestBlock(); - return bitcoindjs.getBlock(hash, callback); -}; - -Daemon.prototype.getChainHeight = function() { - if (daemon.stopping) return []; - return bitcoindjs.getChainHeight(); -}; - -Daemon.prototype.__defineGetter__('chainHeight', function() { - if (daemon.stopping) return []; - return this.getChainHeight(); -}); - -Daemon.prototype.getBlockByTxid = -Daemon.prototype.getBlockByTx = function(txid, callback) { - if (daemon.stopping) return []; - return daemon.db.get('block-tx/' + txid, function(err, block) { - if (block) { - return self.getBlock(block.hash, function(err, block) { - if (err) return callback(err); - var tx_ = block.tx.filter(function(tx) { - return tx.txid === txid; - })[0]; - return callback(null, block, tx_); - }); - } - return bitcoindjs.getBlockByTx(txid, function(err, block, tx_) { - if (err) return callback(err); - daemon.db.set('block-tx/' + txid, { hash: block.hash }, utils.NOOP); - return callback(null, daemon.block(block), daemon.tx(tx_)); - }); - }); -}; - -Daemon.prototype.getBlocksByDate = -Daemon.prototype.getBlocksByTime = function(options, callback) { - if (daemon.stopping) return []; - return bitcoindjs.getBlocksByTime(options, function(err, blocks) { - if (err) return callback(err); - return callback(null, blocks.map(function(block) { - return daemon.block(block) - })); - }); -}; - -Daemon.prototype.getFromTx = function(txid, callback) { - if (daemon.stopping) return []; - return bitcoindjs.getFromTx(txid, function(err, txs) { - if (err) return callback(err); - return callback(null, txs.map(function(tx) { - return daemon.tx(tx) - })); - }); -}; - -Daemon.prototype.getLastFileIndex = function() { - if (daemon.stopping) return []; - return bitcoindjs.getLastFileIndex(); -}; - -Daemon.prototype.log = -Daemon.prototype.info = function() { - if (daemon.stopping) return []; - if (this.options.silent) return; - if (typeof arguments[0] !== 'string') { - var out = util.inspect(arguments[0], null, 20, true); - return process.stdout.write('bitcoind.js: ' + out + '\n'); - } - var out = util.format.apply(util, arguments); - return process.stdout.write('bitcoind.js: ' + out + '\n'); -}; - -Daemon.prototype.error = function() { - if (daemon.stopping) return []; - if (this.options.silent) return; - if (typeof arguments[0] !== 'string') { - var out = util.inspect(arguments[0], null, 20, true); - return process.stderr.write('bitcoind.js: ' + out + '\n'); - } - var out = util.format.apply(util, arguments); - return process.stderr.write('bitcoind.js: ' + out + '\n'); -}; - -Daemon.prototype.stop = -Daemon.prototype.close = function(callback) { - if (daemon.stopping) return []; - var self = this; - return bitcoindjs.stop(function(err, status) { - if (err) { - self.error(err.message); - } else { - self.log(status); - } - if (!callback) return; - return callback(err, status); - }); -}; - -Daemon.prototype.__defineGetter__('stopping', function() { - return bitcoindjs.stopping() || bitcoindjs.stopped(); -}); - -Daemon.prototype.__defineGetter__('stopped', function() { - return bitcoindjs.stopped(); -}); - -Daemon.__defineGetter__('stopping', function() { - return bitcoindjs.stopping() || bitcoindjs.stopped(); -}); - -Daemon.__defineGetter__('stopped', function() { - return bitcoindjs.stopped(); -}); - -/** - * Block - */ - -function Block(data) { - if (!(this instanceof Block)) { - return new Block(data); - } - - if (typeof data === 'string') { - return Block.fromHex(data); - } - - if (data instanceof Block) { - return data; - } - - if (daemon.stopping) return []; - - var self = this; - - Object.keys(data).forEach(function(key) { - if (!self[key]) { - self[key] = data[key]; - } - }); - - this.tx = this.tx.map(function(tx) { - return daemon.tx(tx); - }); - - if (!this.hex) { - this.hex = this.toHex(); - } -} - -Object.defineProperty(Block.prototype, '_blockFlag', { - __proto__: null, - configurable: false, - enumerable: false, - writable: false, - value: {} -}); - -Block.isBlock = function(block) { - if (daemon.stopping) return []; - return block._blockFlag === Block.prototype._blockFlag; -}; - -Block.fromHex = function(hex) { - if (daemon.stopping) return []; - return daemon.block(bitcoindjs.blockFromHex(hex)); -}; - -Block.prototype.getHash = function(enc) { - if (daemon.stopping) return []; - var data = bitcoindjs.getBlockHex(this); - if (!this.hash || this.hash !== data.hash) { - this.hash = data.hash; - } - if (enc === 'hex') return data.hash; - var buf = new Buffer(data.hash, 'hex'); - var out = enc ? buf.toString(enc) : buf; - return out; -}; - -Block.prototype.verify = function() { - if (daemon.stopping) return []; - return this.verified = this.verified || bitcoindjs.verifyBlock(this); -}; - -Block.prototype.toHex = function() { - if (daemon.stopping) return []; - var hex = Block.toHex(this); - if (!this.hex || this.hex !== hex) { - this.hex = hex; - } - return hex; -}; - -Block.toHex = function(block) { - if (daemon.stopping) return []; - var data = bitcoindjs.getBlockHex(block); - return data.hex; -}; - -Block.prototype.toBinary = function() { - if (daemon.stopping) return []; - return Block.toBinary(this); -}; - -Block.toBinary = function(block) { - if (daemon.stopping) return []; - var data = bitcoindjs.getBlockHex(block); - return new Buffer(data.hex, 'hex'); -}; - -/** - * Transaction - */ - -function Transaction(data) { - if (!(this instanceof Transaction)) { - return new Transaction(data); - } - - if (typeof data === 'string') { - return Transaction.fromHex(data); - } - - if (data instanceof Transaction) { - return data; - } - - if (daemon.stopping) return []; - - var self = this; - - Object.keys(data).forEach(function(key) { - if (!self[key]) { - self[key] = data[key]; - } - }); - - if (!this.hex) { - this.hex = this.toHex(); - } -} - -Object.defineProperty(Transaction.prototype, '_txFlag', { - __proto__: null, - configurable: false, - enumerable: false, - writable: false, - value: {} -}); - -Transaction.isTransaction = -Transaction.isTx = function(tx) { - if (daemon.stopping) return []; - return tx._txFlag === Transaction.prototype._txFlag; -}; - -Transaction.fromHex = function(hex) { - if (daemon.stopping) return []; - return daemon.tx(bitcoindjs.txFromHex(hex)); -}; - -Transaction.prototype.verify = function() { - if (daemon.stopping) return []; - return this.verified = this.verified || bitcoindjs.verifyTransaction(this); -}; - -Transaction.prototype.sign = -Transaction.prototype.fill = function(options) { - if (daemon.stopping) return []; - return Transaction.fill(this, options); -}; - -Transaction.sign = -Transaction.fill = function(tx, options) { - if (daemon.stopping) return []; - var isTx = daemon.tx.isTx(tx) - , newTx; - - if (!isTx) { - tx = daemon.tx(tx); - } - - try { - newTx = bitcoindjs.fillTransaction(tx, options || {}); - } catch (e) { - return false; - } - - Object.keys(newTx).forEach(function(key) { - tx[key] = newTx[key]; - }); - - return tx; -}; - -Transaction.prototype.getHash = function(enc) { - if (daemon.stopping) return []; - var data = bitcoindjs.getTxHex(this); - if (!this.txid || this.txid !== data.hash) { - this.txid = data.hash; - } - if (enc === 'hex') return data.hash; - var buf = new Buffer(data.hash, 'hex'); - var out = enc ? buf.toString(enc) : buf; - return out; -}; - -Transaction.prototype.isCoinbase = function() { - if (daemon.stopping) return []; - return this.vin.length === 1 && this.vin[0].coinbase; -}; - -Transaction.prototype.toHex = function() { - if (daemon.stopping) return []; - var hex = Transaction.toHex(this); - if (!this.hex || hex !== this.hex) { - this.hex = hex; - } - return hex; -}; - -Transaction.toHex = function(tx) { - if (daemon.stopping) return []; - var data = bitcoindjs.getTxHex(tx); - return data.hex; -}; - -Transaction.prototype.toBinary = function() { - if (daemon.stopping) return []; - return Transaction.toBinary(this); -}; - -Transaction.toBinary = function(tx) { - if (daemon.stopping) return []; - var data = bitcoindjs.getTxHex(tx); - return new Buffer(data.hex, 'hex'); -}; - -Transaction.broadcast = function(tx, options, callback) { - if (daemon.stopping) return []; - if (typeof tx === 'string') { - tx = { hex: tx }; - } - - if (!callback) { - callback = options; - options = null; - } - - if (!options) { - options = {}; - } - - var fee = options.overrideFees = options.overrideFees || false; - var own = options.ownOnly = options.ownOnly || false; - - if (!callback) { - callback = utils.NOOP; - } - - if (!daemon.isTx(tx)) { - tx = daemon.tx(tx); - } - - return bitcoindjs.broadcastTx(tx, fee, own, function(err, hash, tx) { - if (err) { - if (callback === utils.NOOP) { - daemon.global.emit('error', err); - } - return callback(err); - } - tx = daemon.tx(tx); - daemon.global.emit('broadcast', tx); - return callback(null, hash, tx); - }); -}; - -Transaction.prototype.broadcast = function(options, callback) { - if (daemon.stopping) return []; - if (!callback) { - callback = options; - options = null; - } - return Transaction.broadcast(this, options, callback); -}; - -/** - * Addresses - */ - -function Addresses(data) { - if (!(this instanceof Addresses)) { - return new Addresses(data); - } - - if (data instanceof Addresses) { - return data; - } - - if (daemon.stopping) return []; - - var self = this; - - Object.keys(data).forEach(function(key) { - if (!self[key]) { - self[key] = data[key]; - } - }); -} - -Object.defineProperty(Transaction.prototype, '_addrFlag', { - __proto__: null, - configurable: false, - enumerable: false, - writable: false, - value: {} -}); - -Addresses.isAddresses = -Addresses.isAddr = function(addr) { - if (daemon.stopping) return []; - return addr._txFlag === Addresses.prototype._addrFlag; -}; - -/** - * Utils - */ - -var utils = {}; - -utils.forEach = function(obj, iter, done) { - if (daemon.stopping) return []; - var pending = obj.length; - if (!pending) return done(); - var next = function() { - if (!--pending) done(); - }; - obj.forEach(function(item) { - iter(item, next); - }); -}; - -utils.NOOP = function() {}; - -/** - * Expose - */ - -module.exports = exports = daemon; - -exports.Daemon = daemon; -exports.daemon = daemon; -exports.bitcoind = daemon; - -exports.native = bitcoindjs; -exports.bitcoindjs = bitcoindjs; - -exports.Block = Block; -exports.block = Block; - -exports.Transaction = Transaction; -exports.transaction = Transaction; -exports.tx = Transaction; - -exports.Addresses = Addresses; -exports.addresses = Addresses; -exports.addr = Addresses; - -exports.utils = utils; diff --git a/lib/node.js b/lib/node.js index decc99e6..1c0ef8dd 100644 --- a/lib/node.js +++ b/lib/node.js @@ -74,7 +74,7 @@ Node.prototype._loadBitcoinConf = function(config) { Node.prototype._loadBitcoind = function(config) { var bitcoindConfig = {}; bitcoindConfig.datadir = config.datadir; - bitcoindConfig.testnet = config.testnet; + bitcoindConfig.network = config.network; // start the bitcoind daemon this.bitcoind = daemon(bitcoindConfig); @@ -117,11 +117,23 @@ Node.prototype._syncBitcoind = function() { }; Node.prototype._loadNetwork = function(config) { - if (config.network) { - Networks.add(config.network); - this.network = Networks.get(config.network.name); - } else if (config.testnet) { + if (config.network === 'testnet') { this.network = Networks.get('testnet'); + } else if (config.network === 'regtest') { + Networks.remove(Networks.testnet); + Networks.add({ + name: 'regtest', + alias: 'regtest', + pubkeyhash: 0x6f, + privatekey: 0xef, + scripthash: 0xc4, + xpubkey: 0x043587cf, + xprivkey: 0x04358394, + networkMagic: 0xfabfb5da, + port: 18444, + dnsSeeds: [ ] + }); + this.network = Networks.get('regtest'); } else { this.network = Networks.get('livenet'); } @@ -142,11 +154,14 @@ Node.prototype._loadDB = function(config) { // based on the network configuration and the datadir $.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.testnet) { - config.db.path = datadir + '/testnet3/bitcoindjs.db'; - } else if (this.network === Networks.livenet) { + if (this.network === Networks.livenet) { config.db.path = datadir + '/bitcoindjs.db'; + } else if (this.network === Networks.testnet) { + config.db.path = datadir + '/testnet3/bitcoindjs.db'; + } else if (this.network === regtest) { + config.db.path = datadir + '/regtest/bitcoindjs.db'; } else { throw new Error('Unknown network: ' + this.network); } diff --git a/package.json b/package.json index 74b63809..1183d515 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "preinstall": "./bin/build-libbitcoind", "install": "./bin/build-bindings", "start": "node example", - "debug_install": "./bin/build-libbitcoind debug && ./bin/build-bindings debug", "test": "NODE_ENV=test mocha --recursive", "coverage": "istanbul cover _mocha -- --recursive" }, @@ -51,10 +50,12 @@ "devDependencies": { "benchmark": "1.0.0", "bitcoin": "^2.3.2", + "bitcoind-rpc": "^0.3.0", "bitcore": "^0.12.12", "chai": "^3.0.0", "mocha": "~1.16.2", - "sinon": "^1.15.4", - "proxyquire": "^1.3.1" + "proxyquire": "^1.3.1", + "rimraf": "^2.4.2", + "sinon": "^1.15.4" } } diff --git a/src/bitcoindjs.cc b/src/bitcoindjs.cc index c349de82..82a035c8 100644 --- a/src/bitcoindjs.cc +++ b/src/bitcoindjs.cc @@ -76,6 +76,7 @@ static volatile bool shutdown_complete = false; static char *g_data_dir = NULL; static bool g_rpc = false; static bool g_testnet = false; +static bool g_regtest = false; static bool g_txindex = false; /** @@ -105,6 +106,7 @@ struct async_node_data { std::string datadir; bool rpc; bool testnet; + bool regtest; bool txindex; Eternal callback; }; @@ -298,6 +300,7 @@ NAN_METHOD(StartBitcoind) { std::string datadir = std::string(""); bool rpc = false; bool testnet = false; + bool regtest = false; bool txindex = false; if (args.Length() >= 2 && args[0]->IsObject() && args[1]->IsFunction()) { @@ -309,8 +312,14 @@ NAN_METHOD(StartBitcoind) { if (options->Get(NanNew("rpc"))->IsBoolean()) { rpc = options->Get(NanNew("rpc"))->ToBoolean()->IsTrue(); } - if (options->Get(NanNew("testnet"))->IsBoolean()) { - testnet = options->Get(NanNew("testnet"))->ToBoolean()->IsTrue(); + if (options->Get(NanNew("network"))->IsString()) { + String::Utf8Value network_(options->Get(NanNew("network"))->ToString()); + std::string network = std::string(*network_); + if (network == "testnet") { + testnet = true; + } else if (network == "regtest") { + regtest = true; + } } if (options->Get(NanNew("txindex"))->IsBoolean()) { txindex = options->Get(NanNew("txindex"))->ToBoolean()->IsTrue(); @@ -337,6 +346,7 @@ NAN_METHOD(StartBitcoind) { data->datadir = datadir; data->rpc = rpc; data->testnet = testnet; + data->regtest = regtest; data->txindex = txindex; Eternal eternal(isolate, callback); @@ -370,6 +380,7 @@ async_start_node(uv_work_t *req) { } g_rpc = (bool)data->rpc; g_testnet = (bool)data->testnet; + g_regtest = (bool)data->regtest; g_txindex = (bool)data->txindex; tcgetattr(STDIN_FILENO, &orig_termios); start_node(); @@ -475,6 +486,11 @@ start_node_thread(void) { argc++; } + if (g_regtest) { + argv[argc] = (char *)"-regtest"; + argc++; + } + argv[argc] = (char *)"-txindex"; argc++; diff --git a/test/node.unit.js b/test/node.unit.js index 5d607681..1660836e 100644 --- a/test/node.unit.js +++ b/test/node.unit.js @@ -168,38 +168,24 @@ describe('Bitcoind Node', function() { }); }); describe('#_loadNetwork', function() { - it('should add the network that was listed in the config', function() { - var config = { - network: { - name: 'chainlib', - alias: 'chainlib', - pubkeyhash: 0x1c, - privatekey: 0x1e, - scripthash: 0x28, - xpubkey: 0x02e8de8f, - xprivkey: 0x02e8da54, - networkMagic: 0x0c110907, - port: 9333 - } - }; - var node = new Node(config); - node._loadNetwork(config); - var network = Networks.get('chainlib'); - should.exist(network); - node.network.name.should.equal('chainlib'); - }); it('should use the testnet network if testnet is specified', function() { var config = { - testnet: true + network: 'testnet' }; - var node = new Node(config); node._loadNetwork(config); node.network.name.should.equal('testnet'); }); + it('should use the regtest network if regtest is specified', function() { + var config = { + network: 'regtest' + }; + var node = new Node(config); + node._loadNetwork(config); + node.network.name.should.equal('regtest'); + }); it('should use the livenet network if nothing is specified', function() { var config = {}; - var node = new Node(config); node._loadNetwork(config); node.network.name.should.equal('livenet'); @@ -245,6 +231,54 @@ describe('Bitcoind Node', function() { node._loadDB(config); }).should.throw('Unknown network'); }); + it('should load the db with regtest', function() { + var DB = function(config) { + config.path.should.equal(process.env.HOME + '/.bitcoin/regtest/bitcoindjs.db'); + }; + var config = { + DB: DB, + datadir: '~/.bitcoin' + }; + + var node = new Node(config); + // Switch to use regtest + Networks.remove(Networks.testnet); + Networks.add({ + name: 'regtest', + alias: 'regtest', + pubkeyhash: 0x6f, + privatekey: 0xef, + scripthash: 0xc4, + xpubkey: 0x043587cf, + xprivkey: 0x04358394, + networkMagic: 0xfabfb5da, + port: 18444, + dnsSeeds: [ ] + }); + var regtest = Networks.get('regtest'); + node.network = regtest; + node._loadDB(config); + node.db.should.be.instanceof(DB); + Networks.remove(regtest); + // Add testnet back + Networks.add({ + name: 'testnet', + alias: 'testnet', + pubkeyhash: 0x6f, + privatekey: 0xef, + scripthash: 0xc4, + xpubkey: 0x043587cf, + xprivkey: 0x04358394, + networkMagic: 0x0b110907, + port: 18333, + dnsSeeds: [ + 'testnet-seed.bitcoin.petertodd.org', + 'testnet-seed.bluematt.me', + 'testnet-seed.alexykot.me', + 'testnet-seed.bitcoin.schildbach.de' + ] + }); + }); }); describe('#_loadP2P', function() { it('should load p2p', function() {