diff --git a/README.md b/README.md index f8ca33ad..468cb5a8 100644 --- a/README.md +++ b/README.md @@ -4,39 +4,58 @@ Bitcoind as a node.js module. ## Building -### bitcoind: +### bitcoind - NOTE (to self): Arch is using bitcoin-daemon 0.9.2.1, the latest boost headers in Arch should be correct. +Cloning libbitcoind: + ``` bash -$ cd ~/bitcoin -$ git clean -xdf +$ cd ~ +$ git clone git@github.com:bitpay/libbitcoind.git bitcoin +$ cd bitcoin +``` -... +This is a fork of bitcoin v0.9.0 right now, but it has the ability to compile +bitcoind as a shared object. This may not be ideal yet. -$ git checkout v0.9.2.1 -OR: -$ git checkout v0.9.0 +#### Compiling bticoind as a library -... +``` bash +# ensure clean up +$ make clean +$ find ~/bitcoin -type f -name '*.o' -or -name '*.so' -print0 | xargs -0 rm -f +# create configure file $ ./autogen.sh -... +# configure as a library with -fPIC on all object files +# use --with-incompatible-bdb if necessary +# use --prefix=/usr if necessary +$ ./configure --enable-library --with-incompatible-bdb -$ ./configure --with-incompatible-bdb --prefix=/usr -OR: -$ ./configure --prefix=/usr - -... - -$ time make +# build libbitcoind.so +$ time make library real 31m33.128s user 16m23.930s sys 2m52.310s ``` +`--enable-library` will compile all object files with `-fPIC` (Position +Independent Code - needed to create a shared object). + +`make library` will then compile `./src/libbitcoind.so` (with `-shared -fPIC`), +linking to all the freshly compiled PIC object files. + +Without `--enable-library`, the Makefile with compile bitcoind with -fPIE +(Position Independent for Executable), this allows compiling of bitcoind. + +#### Todo + +- Find a way to compile bitcoind and libbitcoind.so at the same time without + recompiling object files each time? + ### bitcoind.js: - NOTE: This will eventually try to include our included version of boost. @@ -47,6 +66,30 @@ $ cd ~/work/node_modules/bitcoind.js $ PYTHON=/usr/bin/python2.7 make gyp ``` +#### Running bitcoind.js + +You can run bitcoind.js to start downloading the blockchain by doing: + +``` bash +$ node example/ & +bitcoind: log pipe opened: 12 +bitcoind: status="start_node(): bitcoind opened." +``` + +However, if you look at the bitcoind log files: + +``` bash +$ tail -f ~/.bitcoin/debug.log +connect() to [2001:470:c1f2:3::201]:8333 failed: 101 +connect() to [2001:470:6c:778::2]:8333 failed: 101 +connect() to [2001:470:c1f2:3::201]:8333 failed: 101 +``` + +Right now, the `connect(3)` call is failing due to some conflict with node or +libuv I'm guessing. This is being investigated. + +^C (SIGINT) will call `StartShutdown()` in bitcoind on the node thread pool. + ## Contribution and License Agreement If you contribute code to this project, you are implicitly allowing your code diff --git a/binding.gyp b/binding.gyp index 9c77b492..5e9a7b2a 100644 --- a/binding.gyp +++ b/binding.gyp @@ -15,6 +15,7 @@ 'defines': [ 'HAVE_WORKING_BOOST_SLEEP', #'HAVE_WORKING_BOOST_SLEEP_FOR', + 'ENABLE_WALLET', ], 'cflags_cc': [ '-fexceptions', diff --git a/example/index.js b/example/index.js index 71d9961a..9cf85db9 100755 --- a/example/index.js +++ b/example/index.js @@ -2,10 +2,18 @@ var bitcoind = require('../')(); -bitcoind.on('error', function(err) { - console.log('bitcoind: error="%s"', err.message); +bitcoind.start(function(err) { + bitcoind.on('error', function(err) { + console.log('bitcoind: error="%s"', err.message); + }); + bitcoind.on('open', function(status) { + console.log('bitcoind: status="%s"', status); + }); }); -bitcoind.on('open', function(status) { - console.log('bitcoind: status="%s"', status); +process.on('SIGINT', function() { + return bitcoind.stop(function(err) { + if (err) throw err; + return process.exit(0); + }); }); diff --git a/lib/bitcoind.js b/lib/bitcoind.js index 321f5dac..e0f5c424 100644 --- a/lib/bitcoind.js +++ b/lib/bitcoind.js @@ -23,13 +23,23 @@ function Bitcoin(options) { EventEmitter.call(this); this.options = options; +} + +Bitcoin.prototype.__proto__ = EventEmitter.prototype; + +Bitcoin.prototype.start = function(callback) { + var self = this; this.log_pipe = bitcoindjs.start(function(err, status) { + if (callback) { + callback(err); + callback = null; + } if (err) { self.emit('error', err); - return; + } else { + self.emit('open', status); } - self.emit('open', status); }); // bitcoind's boost threads aren't in the thread pool @@ -38,10 +48,8 @@ function Bitcoin(options) { ; }, 10000); - this.log('log pipe opened: %d', this.log_pipe); -} - -Bitcoin.prototype.__proto__ = EventEmitter.prototype; + return this.log('log pipe opened: %d', this.log_pipe); +}; Bitcoin.prototype.log = Bitcoin.prototype.info = function() { @@ -62,10 +70,20 @@ Bitcoin.prototype.error = function() { return process.stderr.write('bitcoind: ' + out + '\n'); }; -Bitcoin.prototype.close = function() { +Bitcoin.prototype.stop = +Bitcoin.prototype.close = function(callback) { + var self = this; clearInterval(this._interval); delete this._interval; - // XXX Call bitcoind's shutdown here + return bitcoindjs.stop(function(err, status) { + if (err) { + self.error(err.message); + } else { + self.log(status); + } + if (!callback) return; + return callback(err, status); + }); }; /** diff --git a/src/bitcoindjs.cc b/src/bitcoindjs.cc index c16af4e6..06a15b71 100644 --- a/src/bitcoindjs.cc +++ b/src/bitcoindjs.cc @@ -78,8 +78,8 @@ extern void (ThreadImport)(std::vector); extern void (DetectShutdownThread)(boost::thread_group*); extern void (StartNode)(boost::thread_group&); extern void (ThreadScriptCheck)(); +extern void (StartShutdown)(); extern int nScriptCheckThreads; -// extern const int DEFAULT_SCRIPTCHECK_THREADS; // static!! #ifdef ENABLE_WALLET extern std::string strWalletFile; extern CWallet *pwalletMain; @@ -108,6 +108,12 @@ async_start_node_work(uv_work_t *req); static void async_start_node_after(uv_work_t *req); +static void +async_stop_node_work(uv_work_t *req); + +static void +async_stop_node_after(uv_work_t *req); + static int start_node(void); @@ -275,6 +281,8 @@ async_start_node_after(uv_work_t *req) { static int start_node(void) { boost::thread_group threadGroup; + + // XXX Run this in a node thread instead to keep the event loop open: boost::thread *detectShutdownThread = NULL; detectShutdownThread = new boost::thread( boost::bind(&DetectShutdownThread, &threadGroup)); @@ -451,6 +459,97 @@ async_parse_logs_after(uv_work_t *req) { delete req; } +/** + * StopBitcoind + * bitcoind.stop(callback) + */ + +NAN_METHOD(StopBitcoind) { + NanScope(); + + if (args.Length() < 1 || !args[0]->IsFunction()) { + return NanThrowError( + "Usage: bitcoind.stop(callback)"); + } + + Local callback = Local::Cast(args[0]); + + // + // Run bitcoind's StartShutdown() on a separate thread. + // + + async_node_data* data_stop_node = new async_node_data(); + data_stop_node->err_msg = NULL; + data_stop_node->result = NULL; + data_stop_node->callback = Persistent::New(callback); + + uv_work_t *req_stop_node = new uv_work_t(); + req_stop_node->data = data_stop_node; + + int status_stop_node = uv_queue_work(uv_default_loop(), + req_stop_node, async_stop_node_work, + (uv_after_work_cb)async_stop_node_after); + + assert(status_stop_node == 0); + + NanReturnValue(Undefined()); +} + +/** + * async_stop_node_work() + * Call StartShutdown() to join the boost threads, which will call Shutdown(). + */ + +static void +async_stop_node_work(uv_work_t *req) { + async_node_data* node_data = static_cast(req->data); + StartShutdown(); + node_data->result = (char *)strdup("stop_node(): bitcoind shutdown."); +} + +/** + * async_stop_node_after() + * Execute our callback. + */ + +static void +async_stop_node_after(uv_work_t *req) { + NanScope(); + async_node_data* node_data = static_cast(req->data); + + if (node_data->err_msg != NULL) { + Local err = Exception::Error(String::New(node_data->err_msg)); + free(node_data->err_msg); + const unsigned argc = 1; + Local argv[argc] = { err }; + TryCatch try_catch; + node_data->callback->Call(Context::GetCurrent()->Global(), argc, argv); + if (try_catch.HasCaught()) { + node::FatalException(try_catch); + } + } else { + const unsigned argc = 2; + Local argv[argc] = { + Local::New(Null()), + Local::New(String::New(node_data->result)) + }; + TryCatch try_catch; + node_data->callback->Call(Context::GetCurrent()->Global(), argc, argv); + if (try_catch.HasCaught()) { + node::FatalException(try_catch); + } + } + + node_data->callback.Dispose(); + + if (node_data->result != NULL) { + free(node_data->result); + } + + delete node_data; + delete req; +} + /** * Init */ @@ -459,6 +558,7 @@ extern "C" void init(Handle target) { NanScope(); NODE_SET_METHOD(target, "start", StartBitcoind); + NODE_SET_METHOD(target, "stop", StopBitcoind); } NODE_MODULE(bitcoindjs, init)