diff --git a/README.md b/README.md index bbb0a1ed..454d646d 100644 --- a/README.md +++ b/README.md @@ -362,10 +362,15 @@ Note that if you already have a bitcore-node database, and you want to query dat - `daemon.start([options], [callback])` - Start the JavaScript Bitcoin node. - `daemon.getBlock(blockHash|blockHeight, callback)` - Get any block asynchronously by block hash or height as a node buffer. -- `daemon.getTransaction(txid, blockhash, callback)` - Get any tx asynchronously by reading it from disk. -- `daemon.log(message), daemon.info(message)` - Log to standard output. -- `daemon.error(message)` - Log to stderr. -- `daemon.close([callback])` - Stop the JavaScript bitcoin node safely, the callback will be called when bitcoind is closed. This will also be done automatically on `process.exit`. It also takes the bitcoind node off the libuv event loop. If the daemon object is the only thing on the event loop. Node will simply close. +- `daemon.isSpent(txid, outputIndex)` - Returns a boolean if a txid and outputIndex is already spent. +- `daemon.getBlockIndex(blockHash)` - Will return the block chain work and previous hash. +- `daemon.estimateFee(blocks)` - Estimates the fees required to have a transaction included in the number of blocks specified as the first argument. +- `daemon.sendTransaction(transaction, allowAbsurdFees)` - Will attempt to add a transaction to the mempool and broadcast to peers. +- `daemon.getTransaction(txid, queryMempool, callback)` - Get any tx asynchronously by reading it from disk, with an argument to optionally not include the mempool. +- `daemon.getTransactionWithBlockInfo(txid, queryMempool, callback)` - Similar to getTransaction but will also include the block timestamp and height. +- `daemon.getMempoolOutputs(address)` - Will return an array of outputs that match an address from the mempool. +- `daemon.getInfo()` - Basic information about the chain including total number of blocks. +- `daemon.stop([callback])` - Stop the JavaScript bitcoin node safely, the callback will be called when bitcoind is closed. This will also be done automatically on `process.exit`. It also takes the bitcoind node off the libuv event loop. If the daemon object is the only thing on the event loop. Node will simply close. ## License diff --git a/example/daemon.js b/bin/start-libbitcoind.js old mode 100755 new mode 100644 similarity index 51% rename from example/daemon.js rename to bin/start-libbitcoind.js index e0aaff0d..23511601 --- a/example/daemon.js +++ b/bin/start-libbitcoind.js @@ -2,32 +2,27 @@ 'use strict'; -/** - * bitcoind.js example - */ +var chainlib = require('chainlib'); +var log = chainlib.log; -process.title = 'bitcore-node'; +process.title = 'libbitcoind'; /** * daemon */ var daemon = require('../').daemon({ datadir: process.env.BITCORENODE_DIR || '~/.bitcoin', - network: 'testnet' + network: process.env.BITCORENODE_NETWORK || 'testnet' }); daemon.on('ready', function() { - console.log('ready'); -}); - -daemon.on('tx', function(txid) { - console.log('txid', txid); + log.info('ready'); }); daemon.on('error', function(err) { - daemon.log('error="%s"', err.message); + log.info('error="%s"', err.message); }); daemon.on('open', function(status) { - daemon.log('status="%s"', status); + log.info('status="%s"', status); }); diff --git a/integration/regtest.js b/integration/regtest.js index 7b632746..28d400d2 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -5,8 +5,11 @@ // functionality by including the wallet in the build. // To run the tests: $ mocha -R spec integration/regtest.js +var chainlib = require('chainlib'); +var log = chainlib.log; + if (process.env.BITCORENODE_ENV !== 'test') { - console.log('Please set the environment variable BITCORENODE_ENV=test and make sure bindings are compiled for testing'); + log.info('Please set the environment variable BITCORENODE_ENV=test and make sure bindings are compiled for testing'); process.exit(); } @@ -63,14 +66,14 @@ describe('Daemon Binding Functionality', function() { }); bitcoind.on('error', function(err) { - bitcoind.log('error="%s"', err.message); + log.error('error="%s"', err.message); }); bitcoind.on('open', function(status) { - bitcoind.log('status="%s"', status); + log.info('status="%s"', status); }); - console.log('Waiting for Bitcoin Core to initialize...'); + log.info('Waiting for Bitcoin Core to initialize...'); bitcoind.on('ready', function() { @@ -82,7 +85,7 @@ describe('Daemon Binding Functionality', function() { pass: 'local321' }); - console.log('Generating 100 blocks...'); + log.info('Generating 100 blocks...'); // Generate enough blocks so that the initial coinbase transactions // can be spent. @@ -98,7 +101,7 @@ describe('Daemon Binding Functionality', function() { // We'll construct a new transaction that will send funds // to a new address with pubkeyhashout for later testing. - console.log('Preparing unspent outputs...'); + log.info('Preparing unspent outputs...'); client.getBalance(function(err, response) { if (err) { @@ -149,7 +152,7 @@ describe('Daemon Binding Functionality', function() { throw err; } - console.log('Testing setup complete!'); + log.info('Testing setup complete!'); done(); }); }); diff --git a/lib/daemon.js b/lib/daemon.js index cafdc408..101cd605 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -1,15 +1,13 @@ -var net = require('net'); +'use strict'; + var EventEmitter = require('events').EventEmitter; var bitcoind = require('bindings')('bitcoind.node'); +var chainlib = require('chainlib'); +var log = chainlib.log; var util = require('util'); -var fs = require('fs'); -var mkdirp = require('mkdirp'); -var tiny = require('tiny').json; var bitcore = require('bitcore'); var $ = bitcore.util.preconditions; -var daemon = Daemon; - function Daemon(options) { var self = this; @@ -31,14 +29,6 @@ function Daemon(options) { this.config = this.datadir + '/bitcoin.conf'; - this.network = Daemon.livenet; - - if (this.options.network === 'testnet') { - this.network = Daemon.testnet; - } else if(this.options.network === 'regtest') { - this.network = Daemon.regtest; - } - Object.keys(exports).forEach(function(key) { self[key] = exports[key]; }); @@ -50,28 +40,7 @@ function Daemon(options) { }); } -Daemon.prototype.__proto__ = EventEmitter.prototype; - -Daemon.livenet = { - name: 'livenet', - peers: [ - // hardcoded peers - ] -}; - -Daemon.testnet = { - name: 'testnet', - peers: [ - // hardcoded peers - ] -}; - -Daemon.regtest = { - name: 'regtest', - peers: [ - // hardcoded peers - ] -}; +util.inherits(Daemon, EventEmitter); // Make sure signal handlers are not overwritten Daemon._signalQueue = []; @@ -91,26 +60,13 @@ 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 + '/.bitcoind.db', - saveIndex: false, - initialCache: false -}); - Daemon.prototype.start = function(options, callback) { var self = this; @@ -124,7 +80,7 @@ Daemon.prototype.start = function(options, callback) { } if (!callback) { - callback = utils.NOOP; + callback = function() {}; } if (this.instances[this.datadir]) { @@ -249,11 +205,9 @@ Daemon.prototype.start = function(options, callback) { this._shutdown = setInterval(function() { if (!self._stoppingSaid && bitcoind.stopping()) { self._stoppingSaid = true; - self.log('shutting down...'); } if (bitcoind.stopped()) { - self.log('shut down.'); clearInterval(self._shutdown); delete self._shutdown; @@ -265,34 +219,17 @@ Daemon.prototype.start = function(options, callback) { process.kill(process.pid, exitCaught.name); }); return; - } - return self._exit(exitCaught); - } - - if (errorCaught !== none) { - if (errorCaught && errorCaught.stack) { + } else if (errorCaught && errorCaught.stack) { console.error(errorCaught.stack); } - return self._exit(0); + return self._exit(exitCaught); } } }, 1000); }; Daemon.prototype.getBlock = function(blockhash, callback) { - if (daemon.stopping) return []; - return bitcoind.getBlock(blockhash, function(err, block) { - if (err) return callback(err); - return callback(null, block); - }); -}; - -Daemon.prototype.getBlockHeight = function(height, callback) { - if (daemon.stopping) return []; - return bitcoind.getBlock(+height, function(err, block) { - if (err) return callback(err); - return callback(null, daemon.block(block)); - }); + return bitcoind.getBlock(blockhash, callback); }; Daemon.prototype.isSpent = function(txid, outputIndex) { @@ -315,53 +252,6 @@ Daemon.prototype.getTransaction = function(txid, queryMempool, callback) { return bitcoind.getTransaction(txid, queryMempool, callback); }; -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 bitcoind.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 bitcoind.getBlock(tx.blockhash, function(err, block) { - if (err) return callback(err); - return callback(null, daemon.tx(tx), daemon.block(block)); - }); - }); -}; - Daemon.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback) { return bitcoind.getTransactionWithBlockInfo(txid, queryMempool, callback); }; @@ -375,185 +265,17 @@ Daemon.prototype.addMempoolUncheckedTransaction = function(txBuffer) { }; Daemon.prototype.getInfo = function() { - if (daemon.stopping) return []; return bitcoind.getInfo(); }; -Daemon.prototype.getPeerInfo = function() { - if (daemon.stopping) return []; - return bitcoind.getPeerInfo(); -}; - -Daemon.prototype.getAddresses = function() { - if (daemon.stopping) return []; - return bitcoind.getAddresses(); -}; - -Daemon.prototype.getProgress = function(callback) { - return bitcoind.getProgress(callback); -}; - -Daemon.prototype.setGenerate = function(options) { - if (daemon.stopping) return []; - return bitcoind.setGenerate(options || {}); -}; - -Daemon.prototype.getGenerate = function(options) { - if (daemon.stopping) return []; - return bitcoind.getGenerate(options || {}); -}; - -Daemon.prototype.getMiningInfo = function() { - if (daemon.stopping) return []; - return bitcoind.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 bitcoind.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 = bitcoind.getBestBlock(); - return bitcoind.getBlock(hash, callback); -}; - -Daemon.prototype.getChainHeight = function() { - if (daemon.stopping) return []; - return bitcoind.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 bitcoind.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 bitcoind.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 bitcoind.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 bitcoind.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 []; +Daemon.prototype.stop = function(callback) { + if (Daemon.stopping) return []; var self = this; return bitcoind.stop(function(err, status) { if (err) { self.error(err.message); } else { - self.log(status); + log.info(status); } if (!callback) return; return callback(err, status); @@ -576,359 +298,4 @@ Daemon.__defineGetter__('stopped', function() { return bitcoind.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(bitcoind.blockFromHex(hex)); -}; - -Block.prototype.getHash = function(enc) { - if (daemon.stopping) return []; - var data = bitcoind.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 || bitcoind.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 = bitcoind.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 = bitcoind.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(bitcoind.txFromHex(hex)); -}; - -Transaction.prototype.verify = function() { - if (daemon.stopping) return []; - return this.verified = this.verified || bitcoind.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 = bitcoind.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 = bitcoind.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 = bitcoind.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 = bitcoind.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 bitcoind.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 = bitcoind; -exports.bitcoind = bitcoind; - -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; +module.exports = Daemon; diff --git a/package.json b/package.json index dc8e2f9f..a5cd18da 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "install": "./bin/build-bindings", "start": "node example", "test": "NODE_ENV=test mocha --recursive", - "coverage": "istanbul cover _mocha -- --recursive" + "coverage": "istanbul cover _mocha -- --recursive", + "libbitcoind": "node bin/start-libbitcoind.js" }, "tags": [ "bitcoin", @@ -44,8 +45,7 @@ "errno": "^0.1.2", "memdown": "^1.0.0", "mkdirp": "0.5.0", - "nan": "1.3.0", - "tiny": "0.0.10" + "nan": "1.3.0" }, "devDependencies": { "benchmark": "1.0.0",