changes for BWS integration

This commit is contained in:
Patrick Nagurny 2015-08-06 16:19:36 -04:00
parent 52cc91e1d7
commit 78bc054bfa
15 changed files with 404 additions and 134 deletions

View File

@ -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);

View File

@ -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']);
socket.on('address/transaction', function(obj) {
console.log(JSON.stringify(obj, null, 2));
});
socket.emit('subscribe', 'transaction');
socket.emit('subscribe', 'address/transaction', ['13FMwCYz3hUhwPcaWuD2M1U2KzfTtvLM89']);

View File

@ -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

View File

@ -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 */

View File

@ -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);
}
};

View File

@ -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;

View File

@ -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();
});
});

View File

@ -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();
});

View File

@ -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;

45
lib/utils.js Normal file
View File

@ -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;

View File

@ -1566,7 +1566,10 @@ NAN_METHOD(GetMempoolOutputs) {
Local<Object> output = NanNew<Object>();
output->Set(NanNew<String>("script"), NanNew<String>(script.ToString()));
output->Set(NanNew<String>("address"), NanNew<String>(psz));
std::string scriptHex = HexStr(script.begin(), script.end());
output->Set(NanNew<String>("script"), NanNew<String>(scriptHex));
uint64_t satoshis = txout.nValue;
output->Set(NanNew<String>("satoshis"), NanNew<Number>(satoshis)); // can't go above 2 ^ 53 -1

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -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);

View File

@ -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();
});