diff --git a/bin/start.js b/bin/start.js index 4da2476c..ea53eaca 100644 --- a/bin/start.js +++ b/bin/start.js @@ -5,6 +5,7 @@ var chainlib = require('chainlib'); var socketio = require('socket.io'); var log = chainlib.log; log.debug = function() {}; +var utils = require('../lib/utils'); var configuration = { datadir: process.env.BITCORENODE_DIR || '~/.bitcoin', @@ -77,11 +78,7 @@ node.on('ready', function() { } if(result) { - if(result.toJSON) { - response.result = result.toJSON(); - } else { - response.result = result; - } + response.result = utils.expandObject(result); } socketCallback(response); @@ -114,11 +111,7 @@ node.on('ready', function() { var results = []; for(var i = 0; i < arguments.length; i++) { - if(arguments[i].toJSON) { - results.push(arguments[i].toJSON()); - } else { - results.push(arguments[i]); - } + results.push(utils.expandObject(arguments[i])); } var params = [event.name].concat(results); diff --git a/example/client.js b/example/client.js index 88a1cf9c..ded2b61e 100644 --- a/example/client.js +++ b/example/client.js @@ -11,7 +11,7 @@ socket.on('disconnect', function(){ var message = { method: 'getOutputs', - params: ['1HTxCVrXuthad6YW5895K98XmVsdMvvBSw', true] + params: ['2NChMRHVCxTPq9KeyvHQUSbfLaQY55Zzzp8', true] }; socket.send(message, function(response) { @@ -37,8 +37,13 @@ socket.send(message2, function(response) { console.log(response.result); }); -socket.on('transaction', function(address, block) { - console.log(address, block); +socket.on('transaction', function(obj) { + console.log(JSON.stringify(obj, null, 2)); }); -socket.emit('subscribe', 'transaction', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']); \ No newline at end of file +socket.on('address/transaction', function(obj) { + console.log(JSON.stringify(obj, null, 2)); +}); + +socket.emit('subscribe', 'transaction'); +socket.emit('subscribe', 'address/transaction', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']); \ No newline at end of file diff --git a/integration/regtest.js b/integration/regtest.js index 1fcd219c..d309da6b 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -306,7 +306,8 @@ describe('Daemon Binding Functionality', function() { var outputs = bitcoind.getMempoolOutputs(changeAddress); var expected = [ { - script: 'OP_DUP OP_HASH160 073b7eae2823efa349e3b9155b8a735526463a0f OP_EQUALVERIFY OP_CHECKSIG', + address: 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up', + script: '76a914073b7eae2823efa349e3b9155b8a735526463a0f88ac', satoshis: 40000, txid: tx.hash, outputIndex: 1 diff --git a/lib/block.js b/lib/block.js index e8499c31..6e5e9c13 100644 --- a/lib/block.js +++ b/lib/block.js @@ -60,10 +60,6 @@ Block.prototype.toObject = function() { }; }; -Block.prototype.toJSON = function() { - return JSON.stringify(this.toObject()); -}; - Block.prototype.headerToBufferWriter = function(bw) { /* jshint maxstatements: 20 */ diff --git a/lib/bus.js b/lib/bus.js index 8772f3f8..325c09e1 100644 --- a/lib/bus.js +++ b/lib/bus.js @@ -11,44 +11,53 @@ function Bus(params) { util.inherits(Bus, events.EventEmitter); Bus.prototype.subscribe = function(name) { - for (var i = 0; i < this.db.modules.length; i++) { + var events = this.db.getPublishEvents(); + + for(var i = 0; i < this.db.modules.length; i++) { var mod = this.db.modules[i]; - var events = mod.getPublishEvents(); - for (var j = 0; j < events.length; j++) { - var event = events[j]; - var params = Array.prototype.slice.call(arguments).slice(1); - params.unshift(this); - if (name === event.name) { - event.subscribe.apply(event.scope, params); - } + events = events.concat(mod.getPublishEvents()); + } + + for (var j = 0; j < events.length; j++) { + var event = events[j]; + var params = Array.prototype.slice.call(arguments).slice(1); + params.unshift(this); + if (name === event.name) { + event.subscribe.apply(event.scope, params); } } }; Bus.prototype.unsubscribe = function(name) { - for (var i = 0; i < this.db.modules.length; i++) { + var events = this.db.getPublishEvents(); + + for(var i = 0; i < this.db.modules.length; i++) { var mod = this.db.modules[i]; - var events = mod.getPublishEvents(); - for (var j = 0; j < events.length; j++) { - var event = events[j]; - var params = Array.prototype.slice.call(arguments).slice(1); - params.unshift(this); - if (name === event.name) { - event.unsubscribe.apply(event.scope, params); - } + events = events.concat(mod.getPublishEvents()); + } + + for (var j = 0; j < events.length; j++) { + var event = events[j]; + var params = Array.prototype.slice.call(arguments).slice(1); + params.unshift(this); + if (name === event.name) { + event.unsubscribe.apply(event.scope, params); } } }; Bus.prototype.close = function() { - // Unsubscribe from all events - for (var i = 0; i < this.db.modules.length; i++) { + var events = this.db.getPublishEvents(); + + for(var i = 0; i < this.db.modules.length; i++) { var mod = this.db.modules[i]; - var events = mod.getPublishEvents(); - for (var j = 0; j < events.length; j++) { - var event = events[j]; - event.unsubscribe.call(event.scope, this); - } + events = events.concat(mod.getPublishEvents()); + } + + // Unsubscribe from all events + for (var j = 0; j < events.length; j++) { + var event = events[j]; + event.unsubscribe.call(event.scope, this); } }; diff --git a/lib/db.js b/lib/db.js index 2420533e..efae1f4f 100644 --- a/lib/db.js +++ b/lib/db.js @@ -32,10 +32,25 @@ function DB(options) { this.modules = []; + this.subscriptions = { + transaction: [], + block: [] + }; } util.inherits(DB, BaseDB); +DB.prototype.initialize = function() { + // Add all db option modules + if(this._modules && this._modules.length) { + for(var i = 0; i < this._modules.length; i++) { + this.addModule(this._modules[i]); + } + } + this.bitcoind.on('tx', this.transactionHandler.bind(this)); + this.emit('ready'); +} + DB.prototype.getBlock = function(hash, callback) { var self = this; @@ -91,6 +106,34 @@ DB.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback }); }; +DB.prototype.sendTransaction = function(tx, callback) { + if(tx instanceof this.Transaction) { + tx = tx.toString(); + } + $.checkArgument(typeof tx === 'string', 'Argument must be a hex string or Transaction'); + + try { + var txid = this.bitcoind.sendTransaction(tx); + return callback(null, txid); + } catch(err) { + return callback(err); + } +}; + +DB.prototype.estimateFee = function(blocks, callback) { + var self = this; + + // For some reason getting fee for 1 block returns -1 + // Until this is resolved, just make it 2 blocks + if(blocks === 1) { + blocks = 2; + } + + setImmediate(function() { + callback(null, self.bitcoind.estimateFee(blocks)); + }); +} + DB.prototype.validateBlockData = function(block, callback) { // bitcoind does the validation setImmediate(callback); @@ -195,6 +238,11 @@ DB.prototype.blockHandler = function(block, add, callback) { var self = this; var operations = []; + // Notify block subscribers + for(var i = 0; i < this.subscriptions.block.length; i++) { + this.subscriptions.transaction[i].emit('block', block.hash); + } + async.eachSeries( this.modules, function(module, next) { @@ -222,7 +270,9 @@ DB.prototype.blockHandler = function(block, add, callback) { DB.prototype.getAPIMethods = function() { var methods = [ ['getBlock', this, this.getBlock, 1], - ['getTransaction', this, this.getTransaction, 2] + ['getTransaction', this, this.getTransaction, 2], + ['sendTransaction', this, this.sendTransaction, 1], + ['estimateFee', this, this.estimateFee, 1] ]; for(var i = 0; i < this.modules.length; i++) { @@ -232,6 +282,23 @@ DB.prototype.getAPIMethods = function() { return methods; }; +DB.prototype.getPublishEvents = function() { + return [ + { + name: 'transaction', + scope: this, + subscribe: this.subscribe.bind(this, 'transaction'), + unsubscribe: this.unsubscribe.bind(this, 'transaction') + }, + { + name: 'block', + scope: this, + subscribe: this.subscribe.bind(this, 'block'), + unsubscribe: this.unsubscribe.bind(this, 'block') + } + ]; +}; + DB.prototype.addModule = function(Module) { var module = new Module({ db: this @@ -240,4 +307,25 @@ DB.prototype.addModule = function(Module) { this.modules.push(module); }; +DB.prototype.subscribe = function(name, emitter) { + this.subscriptions[name].push(emitter); +}; + +DB.prototype.unsubscribe = function(name, emitter) { + var index = this.subscriptions[name].indexOf(emitter); + if(index > -1) { + this.subscriptions[name].splice(index, 1); + } +}; + +DB.prototype.transactionHandler = function(txInfo) { + var tx = bitcore.Transaction().fromBuffer(txInfo.buffer); + for(var i = 0; i < this.subscriptions.transaction.length; i++) { + this.subscriptions.transaction[i].emit('transaction', { + rejected: !txInfo.mempool, + tx: tx + }); + } +}; + module.exports = DB; diff --git a/lib/modules/address.js b/lib/modules/address.js index caac9cef..abf3689c 100644 --- a/lib/modules/address.js +++ b/lib/modules/address.js @@ -18,8 +18,8 @@ var AddressModule = function(options) { BaseModule.call(this, options); this.subscriptions = {}; - this.subscriptions.transaction = {}; - this.subscriptions.balance = {}; + this.subscriptions['address/transaction'] = {}; + this.subscriptions['address/balance'] = {}; this.db.bitcoind.on('tx', this.transactionHandler.bind(this)); @@ -45,17 +45,17 @@ AddressModule.prototype.getAPIMethods = function() { AddressModule.prototype.getPublishEvents = function() { return [ { - name: 'transaction', + name: 'address/transaction', scope: this, - subscribe: this.subscribe.bind(this, 'transaction'), - unsubscribe: this.unsubscribe.bind(this, 'transaction') + subscribe: this.subscribe.bind(this, 'address/transaction'), + unsubscribe: this.unsubscribe.bind(this, 'address/transaction') }, { - name: 'balance', + name: 'address/balance', scope: this, - subscribe: this.subscribe.bind(this, 'balance'), - unsubscribe: this.unsubscribe.bind(this, 'balance') - } + subscribe: this.subscribe.bind(this, 'address/balance'), + unsubscribe: this.unsubscribe.bind(this, 'address/balance') + }, ]; }; @@ -123,7 +123,6 @@ AddressModule.prototype.transactionHandler = function(txInfo) { for (var key in messages) { this.transactionEventHandler(messages[key]); } - }; AddressModule.prototype.blockHandler = function(block, addOutput, callback) { @@ -234,24 +233,24 @@ AddressModule.prototype.blockHandler = function(block, addOutput, callback) { * @param {Boolean} [obj.rejected] - If the transaction was not accepted in the mempool */ AddressModule.prototype.transactionEventHandler = function(obj) { - if(this.subscriptions.transaction[obj.address]) { - var emitters = this.subscriptions.transaction[obj.address]; - for(var k = 0; k < emitters.length; k++) { - emitters[k].emit('transaction', obj); + if(this.subscriptions['address/transaction'][obj.address]) { + var emitters = this.subscriptions['address/transaction'][obj.address]; + for(var i = 0; i < emitters.length; i++) { + emitters[i].emit('address/transaction', obj); } } }; AddressModule.prototype.balanceEventHandler = function(block, address) { - if(this.subscriptions.balance[address]) { - var emitters = this.subscriptions.balance[address]; + if(this.subscriptions['address/balance'][address]) { + var emitters = this.subscriptions['address/balance'][address]; this.getBalance(address, true, function(err, balance) { if(err) { return this.emit(err); } for(var i = 0; i < emitters.length; i++) { - emitters[i].emit('balance', address, balance, block); + emitters[i].emit('address/balance', address, balance, block); } }); } @@ -338,9 +337,11 @@ AddressModule.prototype.getOutputs = function(addressStr, queryMempool, callback address: addressStr, txid: key[3], outputIndex: Number(key[4]), + timestamp: key[2], satoshis: Number(value[0]), script: value[1], - blockHeight: Number(value[2]) + blockHeight: Number(value[2]), + confirmations: self.db.chain.tip.__height - Number(value[2]) + 1 }; outputs.push(output); @@ -371,7 +372,32 @@ AddressModule.prototype.getOutputs = function(addressStr, queryMempool, callback }; -AddressModule.prototype.getUnspentOutputs = function(address, queryMempool, callback) { +AddressModule.prototype.getUnspentOutputs = function(addresses, queryMempool, callback) { + var self = this; + + if(!Array.isArray(addresses)) { + addresses = [addresses]; + } + + var utxos = []; + + async.eachSeries(addresses, function(address, next) { + self.getUnspentOutputsForAddress(address, queryMempool, function(err, unspents) { + if(err && err instanceof errors.NoOutputs) { + return next(); + } else if(err) { + return next(err); + } + + utxos = utxos.concat(unspents); + next(); + }); + }, function(err) { + callback(err, utxos); + }); +}; + +AddressModule.prototype.getUnspentOutputsForAddress = function(address, queryMempool, callback) { var self = this; @@ -427,7 +453,30 @@ AddressModule.prototype.getSpendInfoForOutput = function(txid, outputIndex, call }); }; -AddressModule.prototype.getAddressHistory = function(address, queryMempool, callback) { +AddressModule.prototype.getAddressHistory = function(addresses, queryMempool, callback) { + var self = this; + + if(!Array.isArray(addresses)) { + addresses = [addresses]; + } + + var history = []; + + async.eachSeries(addresses, function(address, next) { + self.getAddressHistoryForAddress(address, queryMempool, function(err, h) { + if(err) { + return next(err); + } + + history = history.concat(h); + next(); + }); + }, function(err) { + callback(err, history); + }); +}; + +AddressModule.prototype.getAddressHistoryForAddress = function(address, queryMempool, callback) { var self = this; var txinfos = {}; @@ -448,12 +497,14 @@ AddressModule.prototype.getAddressHistory = function(address, queryMempool, call } txinfos[transaction.hash] = { + address: address, satoshis: 0, height: transaction.__height, + confirmations: self.db.chain.tip.__height - transaction.__height + 1, timestamp: transaction.__timestamp, outputIndexes: [], inputIndexes: [], - transaction: transaction + tx: transaction }; callback(null, txinfos[transaction.hash]); @@ -490,7 +541,7 @@ AddressModule.prototype.getAddressHistory = function(address, queryMempool, call } txinfo.inputIndexes.push(spendInfo.inputIndex); - txinfo.satoshis -= txinfo.transaction.inputs[spendInfo.inputIndex].output.satoshis; + txinfo.satoshis -= txinfo.tx.inputs[spendInfo.inputIndex].output.satoshis; next(); }); }); diff --git a/lib/node.js b/lib/node.js index 48e5f022..a4f6bee7 100644 --- a/lib/node.js +++ b/lib/node.js @@ -39,7 +39,7 @@ Node.prototype.getAllAPIMethods = function() { }; Node.prototype.getAllPublishEvents = function() { - var events = []; + var events = this.db.getPublishEvents(); for (var i = 0; i < this.db.modules.length; i++) { var mod = this.db.modules[i]; events = events.concat(mod.getPublishEvents()); @@ -407,15 +407,6 @@ Node.prototype._initializeDatabase = function() { // Database this.db.on('ready', function() { - - // Add all db option modules - var modules = self.db._modules; - if(modules && modules.length) { - for(var i = 0; i < modules.length; i++) { - self.db.addModule(modules[i]); - } - } - log.info('Bitcoin Database Ready'); self.chain.initialize(); }); diff --git a/lib/transaction.js b/lib/transaction.js index 3ba209b4..a971837f 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -7,6 +7,7 @@ var chainlib = require('chainlib'); var BaseTransaction = chainlib.Transaction; var BaseDatabase = chainlib.DB; var levelup = chainlib.deps.levelup; +var _ = bitcore.deps._; Transaction.prototype.populateInputs = function(db, poolTransactions, callback) { var self = this; @@ -29,7 +30,7 @@ Transaction.prototype._populateInput = function(db, input, poolTransactions, cal return callback(new Error('Input is expected to have prevTxId as a buffer')); } var txid = input.prevTxId.toString('hex'); - db.getTransaction(txid, false, function(err, prevTx) { + db.getTransaction(txid, true, function(err, prevTx) { if(err instanceof levelup.errors.NotFoundError) { // Check the pool for transaction for(var i = 0; i < poolTransactions.length; i++) { @@ -64,4 +65,39 @@ Transaction.manyToBuffer = function(transactions) { return BaseTransaction.manyToBuffer(transactions); }; +/** + * Override Bitcore's toObject() to include populated inputs and txid + */ +Transaction.prototype.toObject = function toObject() { + var inputs = []; + this.inputs.forEach(function(input) { + var inputObj = input.toObject(); + if(input.output) { + inputObj.output = input.output.toObject(); + } + inputs.push(inputObj); + }); + var outputs = []; + this.outputs.forEach(function(output) { + outputs.push(output.toObject()); + }); + var obj = { + txid: this.id, + version: this.version, + inputs: inputs, + outputs: outputs, + nLockTime: this.nLockTime + }; + if (this._changeScript) { + obj.changeScript = this._changeScript.toString(); + } + if (!_.isUndefined(this._changeIndex)) { + obj.changeIndex = this._changeIndex; + } + if (!_.isUndefined(this._fee)) { + obj.fee = this._fee; + } + return obj; +}; + module.exports = Transaction; diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 00000000..ffdf34b9 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,45 @@ +'use strict'; + +var chainlib = require('chainlib'); +var utils = chainlib.utils; + +/** + * Bitcore's API does not implement toJSON in the standard way. + * This causes issues when doing a JSON.stringify on an object + * which contains Bitcore objects. This custom implmentation + * of stringify accounts for Bitcore objects. + * @param {Object} obj + * @return {String} json + */ +utils.stringify = function(obj) { + return JSON.stringify(utils.expandObject(obj)); +} + +utils.expandObject = function(obj) { + if(Array.isArray(obj)) { + var expandedArray = []; + for(var i = 0; i < obj.length; i++) { + expandedArray.push(utils.expandObject(obj[i])); + } + + return expandedArray; + } else if(typeof obj === 'function' || typeof obj === 'object') { + if(obj.toObject) { + return obj.toObject(); + } else if(obj.toJSON) { + return obj.toJSON(); + } else { + var expandedObj = {}; + + for(var key in obj) { + expandedObj[key] = utils.expandObject(obj[key]); + } + + return expandedObj; + } + } else { + return obj; + } +}; + +module.exports = utils; \ No newline at end of file diff --git a/src/libbitcoind.cc b/src/libbitcoind.cc index 5753eaa8..428e4ae6 100644 --- a/src/libbitcoind.cc +++ b/src/libbitcoind.cc @@ -1566,7 +1566,10 @@ NAN_METHOD(GetMempoolOutputs) { Local output = NanNew(); - output->Set(NanNew("script"), NanNew(script.ToString())); + output->Set(NanNew("address"), NanNew(psz)); + + std::string scriptHex = HexStr(script.begin(), script.end()); + output->Set(NanNew("script"), NanNew(scriptHex)); uint64_t satoshis = txout.nValue; output->Set(NanNew("satoshis"), NanNew(satoshis)); // can't go above 2 ^ 53 -1 diff --git a/test/bus.unit.js b/test/bus.unit.js index a0d4f249..44c4c662 100644 --- a/test/bus.unit.js +++ b/test/bus.unit.js @@ -7,68 +7,107 @@ var Bus = require('../lib/bus'); describe('Bus', function() { describe('#subscribe', function() { - it('will call modules subscribe function with the correct arguments', function() { - var subscribe = sinon.spy(); + it('will call db and modules subscribe function with the correct arguments', function() { + var subscribeDb = sinon.spy(); + var subscribeModule = sinon.spy(); var db = { + getPublishEvents: sinon.stub().returns([ + { + name: 'dbtest', + scope: this, + subscribe: subscribeDb + } + ] + ), modules: [ { getPublishEvents: sinon.stub().returns([ { name: 'test', scope: this, - subscribe: subscribe, + subscribe: subscribeModule, } ]) } ] }; var bus = new Bus({db: db}); + bus.subscribe('dbtest', 'a', 'b', 'c'); bus.subscribe('test', 'a', 'b', 'c'); - subscribe.callCount.should.equal(1); - subscribe.args[0][0].should.equal(bus); - subscribe.args[0][1].should.equal('a'); - subscribe.args[0][2].should.equal('b'); - subscribe.args[0][3].should.equal('c'); + subscribeModule.callCount.should.equal(1); + subscribeDb.callCount.should.equal(1); + subscribeDb.args[0][0].should.equal(bus); + subscribeDb.args[0][1].should.equal('a'); + subscribeDb.args[0][2].should.equal('b'); + subscribeDb.args[0][3].should.equal('c'); + subscribeModule.args[0][0].should.equal(bus); + subscribeModule.args[0][1].should.equal('a'); + subscribeModule.args[0][2].should.equal('b'); + subscribeModule.args[0][3].should.equal('c'); }); }); describe('#unsubscribe', function() { - it('will call modules unsubscribe function with the correct arguments', function() { - var unsubscribe = sinon.spy(); + it('will call db and modules unsubscribe function with the correct arguments', function() { + var unsubscribeDb = sinon.spy(); + var unsubscribeModule = sinon.spy(); var db = { + getPublishEvents: sinon.stub().returns([ + { + name: 'dbtest', + scope: this, + unsubscribe: unsubscribeDb + } + ] + ), modules: [ { getPublishEvents: sinon.stub().returns([ { name: 'test', scope: this, - unsubscribe: unsubscribe + unsubscribe: unsubscribeModule, } ]) } ] }; var bus = new Bus({db: db}); + bus.unsubscribe('dbtest', 'a', 'b', 'c'); bus.unsubscribe('test', 'a', 'b', 'c'); - unsubscribe.callCount.should.equal(1); - unsubscribe.args[0][0].should.equal(bus); - unsubscribe.args[0][1].should.equal('a'); - unsubscribe.args[0][2].should.equal('b'); - unsubscribe.args[0][3].should.equal('c'); + unsubscribeModule.callCount.should.equal(1); + unsubscribeDb.callCount.should.equal(1); + unsubscribeDb.args[0][0].should.equal(bus); + unsubscribeDb.args[0][1].should.equal('a'); + unsubscribeDb.args[0][2].should.equal('b'); + unsubscribeDb.args[0][3].should.equal('c'); + unsubscribeModule.args[0][0].should.equal(bus); + unsubscribeModule.args[0][1].should.equal('a'); + unsubscribeModule.args[0][2].should.equal('b'); + unsubscribeModule.args[0][3].should.equal('c'); }); }); describe('#close', function() { it('will unsubscribe from all events', function() { - var unsubscribe = sinon.spy(); + var unsubscribeDb = sinon.spy(); + var unsubscribeModule = sinon.spy(); var db = { + getPublishEvents: sinon.stub().returns([ + { + name: 'dbtest', + scope: this, + unsubscribe: unsubscribeDb + } + ] + ), modules: [ { getPublishEvents: sinon.stub().returns([ { name: 'test', scope: this, - unsubscribe: unsubscribe + unsubscribe: unsubscribeModule } ]) } @@ -78,9 +117,12 @@ describe('Bus', function() { var bus = new Bus({db: db}); bus.close(); - unsubscribe.callCount.should.equal(1); - unsubscribe.args[0].length.should.equal(1); - unsubscribe.args[0][0].should.equal(bus); + unsubscribeDb.callCount.should.equal(1); + unsubscribeModule.callCount.should.equal(1); + unsubscribeDb.args[0].length.should.equal(1); + unsubscribeDb.args[0][0].should.equal(bus); + unsubscribeModule.args[0].length.should.equal(1); + unsubscribeModule.args[0][0].should.equal(bus); }); }); diff --git a/test/db.unit.js b/test/db.unit.js index b1790bdb..b52a8827 100644 --- a/test/db.unit.js +++ b/test/db.unit.js @@ -301,7 +301,7 @@ describe('Bitcoin DB', function() { var db = new DB({store: memdown}); db.modules = []; var methods = db.getAPIMethods(); - methods.length.should.equal(2); + methods.length.should.equal(4); }); it('should also return modules API methods', function() { @@ -325,7 +325,7 @@ describe('Bitcoin DB', function() { db.modules = [module1, module2]; var methods = db.getAPIMethods(); - methods.length.should.equal(5); + methods.length.should.equal(7); }); }); diff --git a/test/modules/address.unit.js b/test/modules/address.unit.js index ce6ad882..bb528588 100644 --- a/test/modules/address.unit.js +++ b/test/modules/address.unit.js @@ -257,7 +257,7 @@ describe('AddressModule', function() { it('will emit a transaction if there is a subscriber', function(done) { var am = new AddressModule({db: mockdb}); var emitter = new EventEmitter(); - am.subscriptions.transaction = { + am.subscriptions['address/transaction'] = { '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter] }; var block = { @@ -265,7 +265,7 @@ describe('AddressModule', function() { timestamp: new Date() }; var tx = {}; - emitter.on('transaction', function(obj) { + emitter.on('address/transaction', function(obj) { obj.address.should.equal('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'); obj.tx.should.equal(tx); obj.timestamp.should.equal(block.timestamp); @@ -287,13 +287,13 @@ describe('AddressModule', function() { it('will emit a balance if there is a subscriber', function(done) { var am = new AddressModule({db: mockdb}); var emitter = new EventEmitter(); - am.subscriptions.balance = { + am.subscriptions['address/balance'] = { '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter] }; var block = {}; var balance = 1000; am.getBalance = sinon.stub().callsArgWith(2, null, balance); - emitter.on('balance', function(address, bal, b) { + emitter.on('address/balance', function(address, bal, b) { address.should.equal('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'); bal.should.equal(balance); b.should.equal(block); @@ -309,33 +309,33 @@ describe('AddressModule', function() { var emitter = new EventEmitter(); var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'; - var name = 'transaction'; + var name = 'address/transaction'; am.subscribe(name, emitter, [address]); - am.subscriptions.transaction[address].should.deep.equal([emitter]); + am.subscriptions['address/transaction'][address].should.deep.equal([emitter]); var address2 = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W'; am.subscribe(name, emitter, [address2]); - am.subscriptions.transaction[address2].should.deep.equal([emitter]); + am.subscriptions['address/transaction'][address2].should.deep.equal([emitter]); var emitter2 = new EventEmitter(); am.subscribe(name, emitter2, [address]); - am.subscriptions.transaction[address].should.deep.equal([emitter, emitter2]); + am.subscriptions['address/transaction'][address].should.deep.equal([emitter, emitter2]); }); it('will add an emitter to the subscribers array (balance)', function() { var am = new AddressModule({db: mockdb}); var emitter = new EventEmitter(); - var name = 'balance'; + var name = 'address/balance'; var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'; am.subscribe(name, emitter, [address]); - am.subscriptions.balance[address].should.deep.equal([emitter]); + am.subscriptions['address/balance'][address].should.deep.equal([emitter]); var address2 = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W'; am.subscribe(name, emitter, [address2]); - am.subscriptions.balance[address2].should.deep.equal([emitter]); + am.subscriptions['address/balance'][address2].should.deep.equal([emitter]); var emitter2 = new EventEmitter(); am.subscribe(name, emitter2, [address]); - am.subscriptions.balance[address].should.deep.equal([emitter, emitter2]); + am.subscriptions['address/balance'][address].should.deep.equal([emitter, emitter2]); }); }); @@ -345,31 +345,31 @@ describe('AddressModule', function() { var emitter = new EventEmitter(); var emitter2 = new EventEmitter(); var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'; - am.subscriptions.transaction[address] = [emitter, emitter2]; - var name = 'transaction'; + am.subscriptions['address/transaction'][address] = [emitter, emitter2]; + var name = 'address/transaction'; am.unsubscribe(name, emitter, [address]); - am.subscriptions.transaction[address].should.deep.equal([emitter2]); + am.subscriptions['address/transaction'][address].should.deep.equal([emitter2]); }); it('will remove emitter from subscribers array (balance)', function() { var am = new AddressModule({db: mockdb}); var emitter = new EventEmitter(); var emitter2 = new EventEmitter(); var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N'; - var name = 'balance'; - am.subscriptions.balance[address] = [emitter, emitter2]; + var name = 'address/balance'; + am.subscriptions['address/balance'][address] = [emitter, emitter2]; am.unsubscribe(name, emitter, [address]); - am.subscriptions.balance[address].should.deep.equal([emitter2]); + am.subscriptions['address/balance'][address].should.deep.equal([emitter2]); }); it('should unsubscribe from all addresses if no addresses are specified', function() { var am = new AddressModule({db: mockdb}); var emitter = new EventEmitter(); var emitter2 = new EventEmitter(); - am.subscriptions.balance = { + am.subscriptions['address/balance'] = { '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W': [emitter, emitter2], '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter2, emitter] }; - am.unsubscribe('balance', emitter); - am.subscriptions.balance.should.deep.equal({ + am.unsubscribe('address/balance', emitter); + am.subscriptions['address/balance'].should.deep.equal({ '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W': [emitter2], '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter2] }); @@ -408,6 +408,11 @@ describe('AddressModule', function() { var db = { bitcoind: { on: sinon.stub() + }, + chain: { + tip: { + __height: 1 + } } }; @@ -489,7 +494,7 @@ describe('AddressModule', function() { }); }); - describe('#getUnspentOutputs', function() { + describe('#getUnspentOutputsForAddress', function() { it('should filter out spent outputs', function(done) { var outputs = [ { @@ -514,7 +519,7 @@ describe('AddressModule', function() { i++; }; - am.getUnspentOutputs('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) { + am.getUnspentOutputsForAddress('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) { should.not.exist(err); outputs.length.should.equal(2); outputs[0].satoshis.should.equal(1000); @@ -525,7 +530,7 @@ describe('AddressModule', function() { it('should handle an error from getOutputs', function(done) { var am = new AddressModule({db: mockdb}); am.getOutputs = sinon.stub().callsArgWith(2, new Error('error')); - am.getUnspentOutputs('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) { + am.getUnspentOutputsForAddress('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) { should.exist(err); err.message.should.equal('error'); done(); @@ -534,7 +539,7 @@ describe('AddressModule', function() { it('should handle when there are no outputs', function(done) { var am = new AddressModule({db: mockdb}); am.getOutputs = sinon.stub().callsArgWith(2, null, []); - am.getUnspentOutputs('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) { + am.getUnspentOutputsForAddress('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) { should.exist(err); err.should.be.instanceof(errors.NoOutputs); outputs.length.should.equal(0); @@ -709,6 +714,11 @@ describe('AddressModule', function() { }, bitcoind: { on: sinon.stub() + }, + chain: { + tip: { + __height: 1 + } } }; var am = new AddressModule({db: db}); @@ -733,23 +743,23 @@ describe('AddressModule', function() { it('should give transaction history for an address', function(done) { am.getAddressHistory('address', true, function(err, history) { should.not.exist(err); - history[0].transaction.hash.should.equal('tx1'); + history[0].tx.hash.should.equal('tx1'); history[0].satoshis.should.equal(5000); history[0].height.should.equal(1); history[0].timestamp.should.equal(1438289011844); - history[1].transaction.hash.should.equal('tx2'); + history[1].tx.hash.should.equal('tx2'); history[1].satoshis.should.equal(-5000); history[1].height.should.equal(2); history[1].timestamp.should.equal(1438289021844); - history[2].transaction.hash.should.equal('tx3'); + history[2].tx.hash.should.equal('tx3'); history[2].satoshis.should.equal(2000); history[2].height.should.equal(3); history[2].timestamp.should.equal(1438289031844); - history[3].transaction.hash.should.equal('tx4'); + history[3].tx.hash.should.equal('tx4'); history[3].satoshis.should.equal(3000); history[3].height.should.equal(4); history[3].timestamp.should.equal(1438289041844); - history[4].transaction.hash.should.equal('tx5'); + history[4].tx.hash.should.equal('tx5'); history[4].satoshis.should.equal(-3000); history[4].height.should.equal(5); history[4].timestamp.should.equal(1438289051844); diff --git a/test/node.unit.js b/test/node.unit.js index b6de7661..674f3ed4 100644 --- a/test/node.unit.js +++ b/test/node.unit.js @@ -71,6 +71,7 @@ describe('Bitcoind Node', function() { it('should return modules publish events', function() { var node = new Node({}); var db = { + getPublishEvents: sinon.stub().returns(['db1', 'db2']), modules: [ { getPublishEvents: sinon.stub().returns(['mda1', 'mda2']) @@ -83,7 +84,7 @@ describe('Bitcoind Node', function() { node.db = db; var events = node.getAllPublishEvents(); - events.should.deep.equal(['mda1', 'mda2', 'mdb1', 'mdb2']); + events.should.deep.equal(['db1', 'db2', 'mda1', 'mda2', 'mdb1', 'mdb2']); }); }); describe('#_loadConfiguration', function() { @@ -462,7 +463,6 @@ describe('Bitcoind Node', function() { setImmediate(function() { chainlib.log.info.callCount.should.equal(1); chainlib.log.info.restore(); - node.db.addModule.callCount.should.equal(1); node.chain.initialize.callCount.should.equal(1); done(); });