Merge pull request #111 from pnagurny/feature/changes-for-bws
Changes for integrating into BWS
This commit is contained in:
commit
96824feeaa
12
bin/start.js
12
bin/start.js
@ -77,11 +77,7 @@ node.on('ready', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(result) {
|
if(result) {
|
||||||
if(result.toJSON) {
|
response.result = result;
|
||||||
response.result = result.toJSON();
|
|
||||||
} else {
|
|
||||||
response.result = result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
socketCallback(response);
|
socketCallback(response);
|
||||||
@ -114,11 +110,7 @@ node.on('ready', function() {
|
|||||||
var results = [];
|
var results = [];
|
||||||
|
|
||||||
for(var i = 0; i < arguments.length; i++) {
|
for(var i = 0; i < arguments.length; i++) {
|
||||||
if(arguments[i].toJSON) {
|
results.push(arguments[i]);
|
||||||
results.push(arguments[i].toJSON());
|
|
||||||
} else {
|
|
||||||
results.push(arguments[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var params = [event.name].concat(results);
|
var params = [event.name].concat(results);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ socket.on('disconnect', function(){
|
|||||||
|
|
||||||
var message = {
|
var message = {
|
||||||
method: 'getOutputs',
|
method: 'getOutputs',
|
||||||
params: ['1HTxCVrXuthad6YW5895K98XmVsdMvvBSw', true]
|
params: ['2NChMRHVCxTPq9KeyvHQUSbfLaQY55Zzzp8', true]
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.send(message, function(response) {
|
socket.send(message, function(response) {
|
||||||
@ -37,8 +37,13 @@ socket.send(message2, function(response) {
|
|||||||
console.log(response.result);
|
console.log(response.result);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('transaction', function(address, block) {
|
socket.on('transaction', function(obj) {
|
||||||
console.log(address, block);
|
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']);
|
||||||
@ -306,7 +306,8 @@ describe('Daemon Binding Functionality', function() {
|
|||||||
var outputs = bitcoind.getMempoolOutputs(changeAddress);
|
var outputs = bitcoind.getMempoolOutputs(changeAddress);
|
||||||
var expected = [
|
var expected = [
|
||||||
{
|
{
|
||||||
script: 'OP_DUP OP_HASH160 073b7eae2823efa349e3b9155b8a735526463a0f OP_EQUALVERIFY OP_CHECKSIG',
|
address: 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up',
|
||||||
|
script: '76a914073b7eae2823efa349e3b9155b8a735526463a0f88ac',
|
||||||
satoshis: 40000,
|
satoshis: 40000,
|
||||||
txid: tx.hash,
|
txid: tx.hash,
|
||||||
outputIndex: 1
|
outputIndex: 1
|
||||||
|
|||||||
@ -48,8 +48,9 @@ Block.fromBufferReader = function(br) {
|
|||||||
return new Block(obj);
|
return new Block(obj);
|
||||||
};
|
};
|
||||||
|
|
||||||
Block.prototype.toObject = function() {
|
Block.prototype.toObject = Block.prototype.toJSON = function() {
|
||||||
return {
|
return {
|
||||||
|
hash: this.hash,
|
||||||
version: this.version,
|
version: this.version,
|
||||||
prevHash: this.prevHash,
|
prevHash: this.prevHash,
|
||||||
merkleRoot: this.merkleRoot,
|
merkleRoot: this.merkleRoot,
|
||||||
@ -60,10 +61,6 @@ Block.prototype.toObject = function() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Block.prototype.toJSON = function() {
|
|
||||||
return JSON.stringify(this.toObject());
|
|
||||||
};
|
|
||||||
|
|
||||||
Block.prototype.headerToBufferWriter = function(bw) {
|
Block.prototype.headerToBufferWriter = function(bw) {
|
||||||
/* jshint maxstatements: 20 */
|
/* jshint maxstatements: 20 */
|
||||||
|
|
||||||
|
|||||||
59
lib/bus.js
59
lib/bus.js
@ -11,44 +11,53 @@ function Bus(params) {
|
|||||||
util.inherits(Bus, events.EventEmitter);
|
util.inherits(Bus, events.EventEmitter);
|
||||||
|
|
||||||
Bus.prototype.subscribe = function(name) {
|
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 mod = this.db.modules[i];
|
||||||
var events = mod.getPublishEvents();
|
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);
|
for (var j = 0; j < events.length; j++) {
|
||||||
params.unshift(this);
|
var event = events[j];
|
||||||
if (name === event.name) {
|
var params = Array.prototype.slice.call(arguments).slice(1);
|
||||||
event.subscribe.apply(event.scope, params);
|
params.unshift(this);
|
||||||
}
|
if (name === event.name) {
|
||||||
|
event.subscribe.apply(event.scope, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Bus.prototype.unsubscribe = function(name) {
|
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 mod = this.db.modules[i];
|
||||||
var events = mod.getPublishEvents();
|
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);
|
for (var j = 0; j < events.length; j++) {
|
||||||
params.unshift(this);
|
var event = events[j];
|
||||||
if (name === event.name) {
|
var params = Array.prototype.slice.call(arguments).slice(1);
|
||||||
event.unsubscribe.apply(event.scope, params);
|
params.unshift(this);
|
||||||
}
|
if (name === event.name) {
|
||||||
|
event.unsubscribe.apply(event.scope, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Bus.prototype.close = function() {
|
Bus.prototype.close = function() {
|
||||||
// Unsubscribe from all events
|
var events = this.db.getPublishEvents();
|
||||||
for (var i = 0; i < this.db.modules.length; i++) {
|
|
||||||
|
for(var i = 0; i < this.db.modules.length; i++) {
|
||||||
var mod = this.db.modules[i];
|
var mod = this.db.modules[i];
|
||||||
var events = mod.getPublishEvents();
|
events = events.concat(mod.getPublishEvents());
|
||||||
for (var j = 0; j < events.length; j++) {
|
}
|
||||||
var event = events[j];
|
|
||||||
event.unsubscribe.call(event.scope, this);
|
// Unsubscribe from all events
|
||||||
}
|
for (var j = 0; j < events.length; j++) {
|
||||||
|
var event = events[j];
|
||||||
|
event.unsubscribe.call(event.scope, this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
84
lib/db.js
84
lib/db.js
@ -32,10 +32,25 @@ function DB(options) {
|
|||||||
|
|
||||||
this.modules = [];
|
this.modules = [];
|
||||||
|
|
||||||
|
this.subscriptions = {
|
||||||
|
transaction: [],
|
||||||
|
block: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(DB, BaseDB);
|
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) {
|
DB.prototype.getBlock = function(hash, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
@ -91,6 +106,28 @@ 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;
|
||||||
|
|
||||||
|
setImmediate(function() {
|
||||||
|
callback(null, self.bitcoind.estimateFee(blocks));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
DB.prototype.validateBlockData = function(block, callback) {
|
DB.prototype.validateBlockData = function(block, callback) {
|
||||||
// bitcoind does the validation
|
// bitcoind does the validation
|
||||||
setImmediate(callback);
|
setImmediate(callback);
|
||||||
@ -195,6 +232,11 @@ DB.prototype.blockHandler = function(block, add, callback) {
|
|||||||
var self = this;
|
var self = this;
|
||||||
var operations = [];
|
var operations = [];
|
||||||
|
|
||||||
|
// Notify block subscribers
|
||||||
|
for(var i = 0; i < this.subscriptions.block.length; i++) {
|
||||||
|
this.subscriptions.block[i].emit('block', block.hash);
|
||||||
|
}
|
||||||
|
|
||||||
async.eachSeries(
|
async.eachSeries(
|
||||||
this.modules,
|
this.modules,
|
||||||
function(module, next) {
|
function(module, next) {
|
||||||
@ -222,7 +264,9 @@ DB.prototype.blockHandler = function(block, add, callback) {
|
|||||||
DB.prototype.getAPIMethods = function() {
|
DB.prototype.getAPIMethods = function() {
|
||||||
var methods = [
|
var methods = [
|
||||||
['getBlock', this, this.getBlock, 1],
|
['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++) {
|
for(var i = 0; i < this.modules.length; i++) {
|
||||||
@ -232,6 +276,23 @@ DB.prototype.getAPIMethods = function() {
|
|||||||
return methods;
|
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) {
|
DB.prototype.addModule = function(Module) {
|
||||||
var module = new Module({
|
var module = new Module({
|
||||||
db: this
|
db: this
|
||||||
@ -240,4 +301,25 @@ DB.prototype.addModule = function(Module) {
|
|||||||
this.modules.push(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;
|
module.exports = DB;
|
||||||
|
|||||||
@ -18,8 +18,8 @@ var AddressModule = function(options) {
|
|||||||
BaseModule.call(this, options);
|
BaseModule.call(this, options);
|
||||||
|
|
||||||
this.subscriptions = {};
|
this.subscriptions = {};
|
||||||
this.subscriptions.transaction = {};
|
this.subscriptions['address/transaction'] = {};
|
||||||
this.subscriptions.balance = {};
|
this.subscriptions['address/balance'] = {};
|
||||||
|
|
||||||
this.db.bitcoind.on('tx', this.transactionHandler.bind(this));
|
this.db.bitcoind.on('tx', this.transactionHandler.bind(this));
|
||||||
|
|
||||||
@ -45,16 +45,16 @@ AddressModule.prototype.getAPIMethods = function() {
|
|||||||
AddressModule.prototype.getPublishEvents = function() {
|
AddressModule.prototype.getPublishEvents = function() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'transaction',
|
name: 'address/transaction',
|
||||||
scope: this,
|
scope: this,
|
||||||
subscribe: this.subscribe.bind(this, 'transaction'),
|
subscribe: this.subscribe.bind(this, 'address/transaction'),
|
||||||
unsubscribe: this.unsubscribe.bind(this, 'transaction')
|
unsubscribe: this.unsubscribe.bind(this, 'address/transaction')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'balance',
|
name: 'address/balance',
|
||||||
scope: this,
|
scope: this,
|
||||||
subscribe: this.subscribe.bind(this, 'balance'),
|
subscribe: this.subscribe.bind(this, 'address/balance'),
|
||||||
unsubscribe: this.unsubscribe.bind(this, 'balance')
|
unsubscribe: this.unsubscribe.bind(this, 'address/balance')
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@ -123,7 +123,6 @@ AddressModule.prototype.transactionHandler = function(txInfo) {
|
|||||||
for (var key in messages) {
|
for (var key in messages) {
|
||||||
this.transactionEventHandler(messages[key]);
|
this.transactionEventHandler(messages[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressModule.prototype.blockHandler = function(block, addOutput, callback) {
|
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
|
* @param {Boolean} [obj.rejected] - If the transaction was not accepted in the mempool
|
||||||
*/
|
*/
|
||||||
AddressModule.prototype.transactionEventHandler = function(obj) {
|
AddressModule.prototype.transactionEventHandler = function(obj) {
|
||||||
if(this.subscriptions.transaction[obj.address]) {
|
if(this.subscriptions['address/transaction'][obj.address]) {
|
||||||
var emitters = this.subscriptions.transaction[obj.address];
|
var emitters = this.subscriptions['address/transaction'][obj.address];
|
||||||
for(var k = 0; k < emitters.length; k++) {
|
for(var i = 0; i < emitters.length; i++) {
|
||||||
emitters[k].emit('transaction', obj);
|
emitters[i].emit('address/transaction', obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressModule.prototype.balanceEventHandler = function(block, address) {
|
AddressModule.prototype.balanceEventHandler = function(block, address) {
|
||||||
if(this.subscriptions.balance[address]) {
|
if(this.subscriptions['address/balance'][address]) {
|
||||||
var emitters = this.subscriptions.balance[address];
|
var emitters = this.subscriptions['address/balance'][address];
|
||||||
this.getBalance(address, true, function(err, balance) {
|
this.getBalance(address, true, function(err, balance) {
|
||||||
if(err) {
|
if(err) {
|
||||||
return this.emit(err);
|
return this.emit(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(var i = 0; i < emitters.length; i++) {
|
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,
|
address: addressStr,
|
||||||
txid: key[3],
|
txid: key[3],
|
||||||
outputIndex: Number(key[4]),
|
outputIndex: Number(key[4]),
|
||||||
|
timestamp: key[2],
|
||||||
satoshis: Number(value[0]),
|
satoshis: Number(value[0]),
|
||||||
script: value[1],
|
script: value[1],
|
||||||
blockHeight: Number(value[2])
|
blockHeight: Number(value[2]),
|
||||||
|
confirmations: self.db.chain.tip.__height - Number(value[2]) + 1
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs.push(output);
|
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;
|
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 self = this;
|
||||||
|
|
||||||
var txinfos = {};
|
var txinfos = {};
|
||||||
@ -447,13 +496,21 @@ AddressModule.prototype.getAddressHistory = function(address, queryMempool, call
|
|||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var confirmations = 0;
|
||||||
|
if(transaction.__height >= 0) {
|
||||||
|
confirmations = self.db.chain.tip.__height - transaction.__height;
|
||||||
|
}
|
||||||
|
|
||||||
txinfos[transaction.hash] = {
|
txinfos[transaction.hash] = {
|
||||||
|
address: address,
|
||||||
satoshis: 0,
|
satoshis: 0,
|
||||||
height: transaction.__height,
|
height: transaction.__height,
|
||||||
|
confirmations: confirmations,
|
||||||
timestamp: transaction.__timestamp,
|
timestamp: transaction.__timestamp,
|
||||||
|
fees: transaction.getFee(),
|
||||||
outputIndexes: [],
|
outputIndexes: [],
|
||||||
inputIndexes: [],
|
inputIndexes: [],
|
||||||
transaction: transaction
|
tx: transaction
|
||||||
};
|
};
|
||||||
|
|
||||||
callback(null, txinfos[transaction.hash]);
|
callback(null, txinfos[transaction.hash]);
|
||||||
@ -490,7 +547,7 @@ AddressModule.prototype.getAddressHistory = function(address, queryMempool, call
|
|||||||
}
|
}
|
||||||
|
|
||||||
txinfo.inputIndexes.push(spendInfo.inputIndex);
|
txinfo.inputIndexes.push(spendInfo.inputIndex);
|
||||||
txinfo.satoshis -= txinfo.transaction.inputs[spendInfo.inputIndex].output.satoshis;
|
txinfo.satoshis -= txinfo.tx.inputs[spendInfo.inputIndex].output.satoshis;
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
11
lib/node.js
11
lib/node.js
@ -39,7 +39,7 @@ Node.prototype.getAllAPIMethods = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Node.prototype.getAllPublishEvents = function() {
|
Node.prototype.getAllPublishEvents = function() {
|
||||||
var events = [];
|
var events = this.db.getPublishEvents();
|
||||||
for (var i = 0; i < this.db.modules.length; i++) {
|
for (var i = 0; i < this.db.modules.length; i++) {
|
||||||
var mod = this.db.modules[i];
|
var mod = this.db.modules[i];
|
||||||
events = events.concat(mod.getPublishEvents());
|
events = events.concat(mod.getPublishEvents());
|
||||||
@ -407,15 +407,6 @@ Node.prototype._initializeDatabase = function() {
|
|||||||
|
|
||||||
// Database
|
// Database
|
||||||
this.db.on('ready', function() {
|
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');
|
log.info('Bitcoin Database Ready');
|
||||||
self.chain.initialize();
|
self.chain.initialize();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,6 +7,7 @@ var chainlib = require('chainlib');
|
|||||||
var BaseTransaction = chainlib.Transaction;
|
var BaseTransaction = chainlib.Transaction;
|
||||||
var BaseDatabase = chainlib.DB;
|
var BaseDatabase = chainlib.DB;
|
||||||
var levelup = chainlib.deps.levelup;
|
var levelup = chainlib.deps.levelup;
|
||||||
|
var _ = bitcore.deps._;
|
||||||
|
|
||||||
Transaction.prototype.populateInputs = function(db, poolTransactions, callback) {
|
Transaction.prototype.populateInputs = function(db, poolTransactions, callback) {
|
||||||
var self = this;
|
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'));
|
return callback(new Error('Input is expected to have prevTxId as a buffer'));
|
||||||
}
|
}
|
||||||
var txid = input.prevTxId.toString('hex');
|
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) {
|
if(err instanceof levelup.errors.NotFoundError) {
|
||||||
// Check the pool for transaction
|
// Check the pool for transaction
|
||||||
for(var i = 0; i < poolTransactions.length; i++) {
|
for(var i = 0; i < poolTransactions.length; i++) {
|
||||||
|
|||||||
@ -44,8 +44,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "1.3.0",
|
"async": "1.3.0",
|
||||||
"bindings": "^1.2.1",
|
"bindings": "^1.2.1",
|
||||||
"bitcore": "^0.12.15",
|
"bitcore": "^0.13.0",
|
||||||
"chainlib": "^0.1.1",
|
"chainlib": "^0.1.3",
|
||||||
"errno": "^0.1.2",
|
"errno": "^0.1.2",
|
||||||
"memdown": "^1.0.0",
|
"memdown": "^1.0.0",
|
||||||
"mkdirp": "0.5.0",
|
"mkdirp": "0.5.0",
|
||||||
|
|||||||
@ -1566,7 +1566,10 @@ NAN_METHOD(GetMempoolOutputs) {
|
|||||||
|
|
||||||
Local<Object> output = NanNew<Object>();
|
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;
|
uint64_t satoshis = txout.nValue;
|
||||||
output->Set(NanNew<String>("satoshis"), NanNew<Number>(satoshis)); // can't go above 2 ^ 53 -1
|
output->Set(NanNew<String>("satoshis"), NanNew<Number>(satoshis)); // can't go above 2 ^ 53 -1
|
||||||
|
|||||||
@ -7,68 +7,107 @@ var Bus = require('../lib/bus');
|
|||||||
describe('Bus', function() {
|
describe('Bus', function() {
|
||||||
|
|
||||||
describe('#subscribe', function() {
|
describe('#subscribe', function() {
|
||||||
it('will call modules subscribe function with the correct arguments', function() {
|
it('will call db and modules subscribe function with the correct arguments', function() {
|
||||||
var subscribe = sinon.spy();
|
var subscribeDb = sinon.spy();
|
||||||
|
var subscribeModule = sinon.spy();
|
||||||
var db = {
|
var db = {
|
||||||
|
getPublishEvents: sinon.stub().returns([
|
||||||
|
{
|
||||||
|
name: 'dbtest',
|
||||||
|
scope: this,
|
||||||
|
subscribe: subscribeDb
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
modules: [
|
modules: [
|
||||||
{
|
{
|
||||||
getPublishEvents: sinon.stub().returns([
|
getPublishEvents: sinon.stub().returns([
|
||||||
{
|
{
|
||||||
name: 'test',
|
name: 'test',
|
||||||
scope: this,
|
scope: this,
|
||||||
subscribe: subscribe,
|
subscribe: subscribeModule,
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
var bus = new Bus({db: db});
|
var bus = new Bus({db: db});
|
||||||
|
bus.subscribe('dbtest', 'a', 'b', 'c');
|
||||||
bus.subscribe('test', 'a', 'b', 'c');
|
bus.subscribe('test', 'a', 'b', 'c');
|
||||||
subscribe.callCount.should.equal(1);
|
subscribeModule.callCount.should.equal(1);
|
||||||
subscribe.args[0][0].should.equal(bus);
|
subscribeDb.callCount.should.equal(1);
|
||||||
subscribe.args[0][1].should.equal('a');
|
subscribeDb.args[0][0].should.equal(bus);
|
||||||
subscribe.args[0][2].should.equal('b');
|
subscribeDb.args[0][1].should.equal('a');
|
||||||
subscribe.args[0][3].should.equal('c');
|
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() {
|
describe('#unsubscribe', function() {
|
||||||
it('will call modules unsubscribe function with the correct arguments', function() {
|
it('will call db and modules unsubscribe function with the correct arguments', function() {
|
||||||
var unsubscribe = sinon.spy();
|
var unsubscribeDb = sinon.spy();
|
||||||
|
var unsubscribeModule = sinon.spy();
|
||||||
var db = {
|
var db = {
|
||||||
|
getPublishEvents: sinon.stub().returns([
|
||||||
|
{
|
||||||
|
name: 'dbtest',
|
||||||
|
scope: this,
|
||||||
|
unsubscribe: unsubscribeDb
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
modules: [
|
modules: [
|
||||||
{
|
{
|
||||||
getPublishEvents: sinon.stub().returns([
|
getPublishEvents: sinon.stub().returns([
|
||||||
{
|
{
|
||||||
name: 'test',
|
name: 'test',
|
||||||
scope: this,
|
scope: this,
|
||||||
unsubscribe: unsubscribe
|
unsubscribe: unsubscribeModule,
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
var bus = new Bus({db: db});
|
var bus = new Bus({db: db});
|
||||||
|
bus.unsubscribe('dbtest', 'a', 'b', 'c');
|
||||||
bus.unsubscribe('test', 'a', 'b', 'c');
|
bus.unsubscribe('test', 'a', 'b', 'c');
|
||||||
unsubscribe.callCount.should.equal(1);
|
unsubscribeModule.callCount.should.equal(1);
|
||||||
unsubscribe.args[0][0].should.equal(bus);
|
unsubscribeDb.callCount.should.equal(1);
|
||||||
unsubscribe.args[0][1].should.equal('a');
|
unsubscribeDb.args[0][0].should.equal(bus);
|
||||||
unsubscribe.args[0][2].should.equal('b');
|
unsubscribeDb.args[0][1].should.equal('a');
|
||||||
unsubscribe.args[0][3].should.equal('c');
|
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() {
|
describe('#close', function() {
|
||||||
it('will unsubscribe from all events', function() {
|
it('will unsubscribe from all events', function() {
|
||||||
var unsubscribe = sinon.spy();
|
var unsubscribeDb = sinon.spy();
|
||||||
|
var unsubscribeModule = sinon.spy();
|
||||||
var db = {
|
var db = {
|
||||||
|
getPublishEvents: sinon.stub().returns([
|
||||||
|
{
|
||||||
|
name: 'dbtest',
|
||||||
|
scope: this,
|
||||||
|
unsubscribe: unsubscribeDb
|
||||||
|
}
|
||||||
|
]
|
||||||
|
),
|
||||||
modules: [
|
modules: [
|
||||||
{
|
{
|
||||||
getPublishEvents: sinon.stub().returns([
|
getPublishEvents: sinon.stub().returns([
|
||||||
{
|
{
|
||||||
name: 'test',
|
name: 'test',
|
||||||
scope: this,
|
scope: this,
|
||||||
unsubscribe: unsubscribe
|
unsubscribe: unsubscribeModule
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@ -78,9 +117,12 @@ describe('Bus', function() {
|
|||||||
var bus = new Bus({db: db});
|
var bus = new Bus({db: db});
|
||||||
bus.close();
|
bus.close();
|
||||||
|
|
||||||
unsubscribe.callCount.should.equal(1);
|
unsubscribeDb.callCount.should.equal(1);
|
||||||
unsubscribe.args[0].length.should.equal(1);
|
unsubscribeModule.callCount.should.equal(1);
|
||||||
unsubscribe.args[0][0].should.equal(bus);
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
125
test/db.unit.js
125
test/db.unit.js
@ -10,10 +10,25 @@ var errors = bitcoindjs.errors;
|
|||||||
var memdown = require('memdown');
|
var memdown = require('memdown');
|
||||||
var inherits = require('util').inherits;
|
var inherits = require('util').inherits;
|
||||||
var BaseModule = require('../lib/module');
|
var BaseModule = require('../lib/module');
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var Transaction = bitcore.Transaction;
|
||||||
|
|
||||||
describe('Bitcoin DB', function() {
|
describe('Bitcoin DB', function() {
|
||||||
var coinbaseAmount = 50 * 1e8;
|
var coinbaseAmount = 50 * 1e8;
|
||||||
|
|
||||||
|
describe('#initialize', function() {
|
||||||
|
it('should emit ready', function(done) {
|
||||||
|
var db = new DB({store: memdown});
|
||||||
|
db._modules = ['mod1', 'mod2'];
|
||||||
|
db.bitcoind = {
|
||||||
|
on: sinon.spy()
|
||||||
|
};
|
||||||
|
db.addModule = sinon.spy();
|
||||||
|
db.on('ready', done);
|
||||||
|
db.initialize();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#getTransaction', function() {
|
describe('#getTransaction', function() {
|
||||||
it('will return a NotFound error', function(done) {
|
it('will return a NotFound error', function(done) {
|
||||||
var db = new DB({store: memdown});
|
var db = new DB({store: memdown});
|
||||||
@ -89,6 +104,112 @@ describe('Bitcoin DB', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getPrevHash', function() {
|
||||||
|
it('should return prevHash from bitcoind', function(done) {
|
||||||
|
var db = new DB({store: memdown});
|
||||||
|
db.bitcoind = {
|
||||||
|
getBlockIndex: sinon.stub().returns({
|
||||||
|
prevHash: 'prevhash'
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
db.getPrevHash('hash', function(err, prevHash) {
|
||||||
|
should.not.exist(err);
|
||||||
|
prevHash.should.equal('prevhash');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should give an error if bitcoind could not find it', function(done) {
|
||||||
|
var db = new DB({store: memdown});
|
||||||
|
db.bitcoind = {
|
||||||
|
getBlockIndex: sinon.stub().returns(null)
|
||||||
|
};
|
||||||
|
|
||||||
|
db.getPrevHash('hash', function(err, prevHash) {
|
||||||
|
should.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getTransactionWithBlockInfo', function() {
|
||||||
|
it('should give a transaction with height and timestamp', function(done) {
|
||||||
|
var txBuffer = new Buffer('01000000016f95980911e01c2c664b3e78299527a47933aac61a515930a8fe0213d1ac9abe01000000da0047304402200e71cda1f71e087c018759ba3427eb968a9ea0b1decd24147f91544629b17b4f0220555ee111ed0fc0f751ffebf097bdf40da0154466eb044e72b6b3dcd5f06807fa01483045022100c86d6c8b417bff6cc3bbf4854c16bba0aaca957e8f73e19f37216e2b06bb7bf802205a37be2f57a83a1b5a8cc511dc61466c11e9ba053c363302e7b99674be6a49fc0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9148a31d53a448c18996e81ce67811e5fb7da21e4468738c9d6f90000000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000', 'hex');
|
||||||
|
var info = {
|
||||||
|
height: 530482,
|
||||||
|
timestamp: 1439559434000,
|
||||||
|
buffer: txBuffer
|
||||||
|
};
|
||||||
|
|
||||||
|
var db = new DB({store: memdown});
|
||||||
|
db.bitcoind = {
|
||||||
|
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, null, info)
|
||||||
|
};
|
||||||
|
|
||||||
|
db.getTransactionWithBlockInfo('2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f', true, function(err, tx) {
|
||||||
|
should.not.exist(err);
|
||||||
|
tx.__height.should.equal(info.height);
|
||||||
|
tx.__timestamp.should.equal(info.timestamp);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should give an error if one occurred', function(done) {
|
||||||
|
var db = new DB({store: memdown});
|
||||||
|
db.bitcoind = {
|
||||||
|
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, new Error('error'))
|
||||||
|
};
|
||||||
|
|
||||||
|
db.getTransactionWithBlockInfo('tx', true, function(err, tx) {
|
||||||
|
should.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#sendTransaction', function() {
|
||||||
|
it('should give the txid on success', function(done) {
|
||||||
|
var db = new DB({store: memdown});
|
||||||
|
db.bitcoind = {
|
||||||
|
sendTransaction: sinon.stub().returns('txid')
|
||||||
|
};
|
||||||
|
|
||||||
|
var tx = new Transaction();
|
||||||
|
db.sendTransaction(tx, function(err, txid) {
|
||||||
|
should.not.exist(err);
|
||||||
|
txid.should.equal('txid');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should give an error if bitcoind threw an error', function(done) {
|
||||||
|
var db = new DB({store: memdown});
|
||||||
|
db.bitcoind = {
|
||||||
|
sendTransaction: sinon.stub().throws(new Error('error'))
|
||||||
|
};
|
||||||
|
|
||||||
|
var tx = new Transaction();
|
||||||
|
db.sendTransaction(tx, function(err, txid) {
|
||||||
|
should.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#estimateFee", function() {
|
||||||
|
it('should pass along the fee from bitcoind', function(done) {
|
||||||
|
var db = new DB({store: memdown});
|
||||||
|
db.bitcoind = {
|
||||||
|
estimateFee: sinon.stub().returns(1000)
|
||||||
|
};
|
||||||
|
|
||||||
|
db.estimateFee(5, function(err, fee) {
|
||||||
|
should.not.exist(err);
|
||||||
|
fee.should.equal(1000);
|
||||||
|
db.bitcoind.estimateFee.args[0][0].should.equal(5);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('#buildGenesisData', function() {
|
describe('#buildGenesisData', function() {
|
||||||
it('build genisis data', function() {
|
it('build genisis data', function() {
|
||||||
var db = new DB({path: 'path', store: memdown});
|
var db = new DB({path: 'path', store: memdown});
|
||||||
@ -301,7 +422,7 @@ describe('Bitcoin DB', function() {
|
|||||||
var db = new DB({store: memdown});
|
var db = new DB({store: memdown});
|
||||||
db.modules = [];
|
db.modules = [];
|
||||||
var methods = db.getAPIMethods();
|
var methods = db.getAPIMethods();
|
||||||
methods.length.should.equal(2);
|
methods.length.should.equal(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should also return modules API methods', function() {
|
it('should also return modules API methods', function() {
|
||||||
@ -325,7 +446,7 @@ describe('Bitcoin DB', function() {
|
|||||||
db.modules = [module1, module2];
|
db.modules = [module1, module2];
|
||||||
|
|
||||||
var methods = db.getAPIMethods();
|
var methods = db.getAPIMethods();
|
||||||
methods.length.should.equal(5);
|
methods.length.should.equal(7);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
var should = require('chai').should();
|
var should = require('chai').should();
|
||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
var bitcoindjs = require('../../');
|
var bitcorenode = require('../../');
|
||||||
var AddressModule = bitcoindjs.modules.AddressModule;
|
var AddressModule = bitcorenode.modules.AddressModule;
|
||||||
var blockData = require('../data/livenet-345003.json');
|
var blockData = require('../data/livenet-345003.json');
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var errors = bitcoindjs.errors;
|
var errors = bitcorenode.errors;
|
||||||
var chainlib = require('chainlib');
|
var chainlib = require('chainlib');
|
||||||
var levelup = chainlib.deps.levelup;
|
var levelup = chainlib.deps.levelup;
|
||||||
|
|
||||||
@ -257,7 +257,7 @@ describe('AddressModule', function() {
|
|||||||
it('will emit a transaction if there is a subscriber', function(done) {
|
it('will emit a transaction if there is a subscriber', function(done) {
|
||||||
var am = new AddressModule({db: mockdb});
|
var am = new AddressModule({db: mockdb});
|
||||||
var emitter = new EventEmitter();
|
var emitter = new EventEmitter();
|
||||||
am.subscriptions.transaction = {
|
am.subscriptions['address/transaction'] = {
|
||||||
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter]
|
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter]
|
||||||
};
|
};
|
||||||
var block = {
|
var block = {
|
||||||
@ -265,7 +265,7 @@ describe('AddressModule', function() {
|
|||||||
timestamp: new Date()
|
timestamp: new Date()
|
||||||
};
|
};
|
||||||
var tx = {};
|
var tx = {};
|
||||||
emitter.on('transaction', function(obj) {
|
emitter.on('address/transaction', function(obj) {
|
||||||
obj.address.should.equal('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
|
obj.address.should.equal('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
|
||||||
obj.tx.should.equal(tx);
|
obj.tx.should.equal(tx);
|
||||||
obj.timestamp.should.equal(block.timestamp);
|
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) {
|
it('will emit a balance if there is a subscriber', function(done) {
|
||||||
var am = new AddressModule({db: mockdb});
|
var am = new AddressModule({db: mockdb});
|
||||||
var emitter = new EventEmitter();
|
var emitter = new EventEmitter();
|
||||||
am.subscriptions.balance = {
|
am.subscriptions['address/balance'] = {
|
||||||
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter]
|
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter]
|
||||||
};
|
};
|
||||||
var block = {};
|
var block = {};
|
||||||
var balance = 1000;
|
var balance = 1000;
|
||||||
am.getBalance = sinon.stub().callsArgWith(2, null, balance);
|
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');
|
address.should.equal('1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N');
|
||||||
bal.should.equal(balance);
|
bal.should.equal(balance);
|
||||||
b.should.equal(block);
|
b.should.equal(block);
|
||||||
@ -309,33 +309,33 @@ describe('AddressModule', function() {
|
|||||||
var emitter = new EventEmitter();
|
var emitter = new EventEmitter();
|
||||||
|
|
||||||
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
|
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
|
||||||
var name = 'transaction';
|
var name = 'address/transaction';
|
||||||
am.subscribe(name, emitter, [address]);
|
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';
|
var address2 = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
|
||||||
am.subscribe(name, emitter, [address2]);
|
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();
|
var emitter2 = new EventEmitter();
|
||||||
am.subscribe(name, emitter2, [address]);
|
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() {
|
it('will add an emitter to the subscribers array (balance)', function() {
|
||||||
var am = new AddressModule({db: mockdb});
|
var am = new AddressModule({db: mockdb});
|
||||||
var emitter = new EventEmitter();
|
var emitter = new EventEmitter();
|
||||||
var name = 'balance';
|
var name = 'address/balance';
|
||||||
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
|
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
|
||||||
am.subscribe(name, emitter, [address]);
|
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';
|
var address2 = '1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W';
|
||||||
am.subscribe(name, emitter, [address2]);
|
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();
|
var emitter2 = new EventEmitter();
|
||||||
am.subscribe(name, emitter2, [address]);
|
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 emitter = new EventEmitter();
|
||||||
var emitter2 = new EventEmitter();
|
var emitter2 = new EventEmitter();
|
||||||
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
|
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
|
||||||
am.subscriptions.transaction[address] = [emitter, emitter2];
|
am.subscriptions['address/transaction'][address] = [emitter, emitter2];
|
||||||
var name = 'transaction';
|
var name = 'address/transaction';
|
||||||
am.unsubscribe(name, emitter, [address]);
|
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() {
|
it('will remove emitter from subscribers array (balance)', function() {
|
||||||
var am = new AddressModule({db: mockdb});
|
var am = new AddressModule({db: mockdb});
|
||||||
var emitter = new EventEmitter();
|
var emitter = new EventEmitter();
|
||||||
var emitter2 = new EventEmitter();
|
var emitter2 = new EventEmitter();
|
||||||
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
|
var address = '1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N';
|
||||||
var name = 'balance';
|
var name = 'address/balance';
|
||||||
am.subscriptions.balance[address] = [emitter, emitter2];
|
am.subscriptions['address/balance'][address] = [emitter, emitter2];
|
||||||
am.unsubscribe(name, emitter, [address]);
|
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() {
|
it('should unsubscribe from all addresses if no addresses are specified', function() {
|
||||||
var am = new AddressModule({db: mockdb});
|
var am = new AddressModule({db: mockdb});
|
||||||
var emitter = new EventEmitter();
|
var emitter = new EventEmitter();
|
||||||
var emitter2 = new EventEmitter();
|
var emitter2 = new EventEmitter();
|
||||||
am.subscriptions.balance = {
|
am.subscriptions['address/balance'] = {
|
||||||
'1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W': [emitter, emitter2],
|
'1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W': [emitter, emitter2],
|
||||||
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter2, emitter]
|
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter2, emitter]
|
||||||
};
|
};
|
||||||
am.unsubscribe('balance', emitter);
|
am.unsubscribe('address/balance', emitter);
|
||||||
am.subscriptions.balance.should.deep.equal({
|
am.subscriptions['address/balance'].should.deep.equal({
|
||||||
'1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W': [emitter2],
|
'1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W': [emitter2],
|
||||||
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter2]
|
'1DzjESe6SLmAKVPLFMj6Sx1sWki3qt5i8N': [emitter2]
|
||||||
});
|
});
|
||||||
@ -408,6 +408,11 @@ describe('AddressModule', function() {
|
|||||||
var db = {
|
var db = {
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
on: sinon.stub()
|
on: sinon.stub()
|
||||||
|
},
|
||||||
|
chain: {
|
||||||
|
tip: {
|
||||||
|
__height: 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -490,6 +495,94 @@ describe('AddressModule', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('#getUnspentOutputs', function() {
|
describe('#getUnspentOutputs', function() {
|
||||||
|
it('should concatenate utxos for multiple addresses, even those with none found', function(done) {
|
||||||
|
var addresses = {
|
||||||
|
'addr1': ['utxo1', 'utxo2'],
|
||||||
|
'addr2': new errors.NoOutputs(),
|
||||||
|
'addr3': ['utxo3']
|
||||||
|
};
|
||||||
|
|
||||||
|
var db = {
|
||||||
|
bitcoind: {
|
||||||
|
on: sinon.spy()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var am = new AddressModule({db: db});
|
||||||
|
am.getUnspentOutputsForAddress = function(address, queryMempool, callback) {
|
||||||
|
var result = addresses[address];
|
||||||
|
if(result instanceof Error) {
|
||||||
|
return callback(result);
|
||||||
|
} else {
|
||||||
|
return callback(null, result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
am.getUnspentOutputs(['addr1', 'addr2', 'addr3'], true, function(err, utxos) {
|
||||||
|
should.not.exist(err);
|
||||||
|
utxos.should.deep.equal(['utxo1', 'utxo2', 'utxo3']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should give an error if an error occurred', function(done) {
|
||||||
|
var addresses = {
|
||||||
|
'addr1': ['utxo1', 'utxo2'],
|
||||||
|
'addr2': new Error('weird error'),
|
||||||
|
'addr3': ['utxo3']
|
||||||
|
};
|
||||||
|
|
||||||
|
var db = {
|
||||||
|
bitcoind: {
|
||||||
|
on: sinon.spy()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var am = new AddressModule({db: db});
|
||||||
|
am.getUnspentOutputsForAddress = function(address, queryMempool, callback) {
|
||||||
|
var result = addresses[address];
|
||||||
|
if(result instanceof Error) {
|
||||||
|
return callback(result);
|
||||||
|
} else {
|
||||||
|
return callback(null, result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
am.getUnspentOutputs(['addr1', 'addr2', 'addr3'], true, function(err, utxos) {
|
||||||
|
should.exist(err);
|
||||||
|
err.message.should.equal('weird error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should also work for a single address', function(done) {
|
||||||
|
var addresses = {
|
||||||
|
'addr1': ['utxo1', 'utxo2'],
|
||||||
|
'addr2': new Error('weird error'),
|
||||||
|
'addr3': ['utxo3']
|
||||||
|
};
|
||||||
|
|
||||||
|
var db = {
|
||||||
|
bitcoind: {
|
||||||
|
on: sinon.spy()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var am = new AddressModule({db: db});
|
||||||
|
am.getUnspentOutputsForAddress = function(address, queryMempool, callback) {
|
||||||
|
var result = addresses[address];
|
||||||
|
if(result instanceof Error) {
|
||||||
|
return callback(result);
|
||||||
|
} else {
|
||||||
|
return callback(null, result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
am.getUnspentOutputs('addr1', true, function(err, utxos) {
|
||||||
|
should.not.exist(err);
|
||||||
|
utxos.should.deep.equal(['utxo1', 'utxo2']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getUnspentOutputsForAddress', function() {
|
||||||
it('should filter out spent outputs', function(done) {
|
it('should filter out spent outputs', function(done) {
|
||||||
var outputs = [
|
var outputs = [
|
||||||
{
|
{
|
||||||
@ -514,7 +607,7 @@ describe('AddressModule', function() {
|
|||||||
i++;
|
i++;
|
||||||
};
|
};
|
||||||
|
|
||||||
am.getUnspentOutputs('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) {
|
am.getUnspentOutputsForAddress('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
outputs.length.should.equal(2);
|
outputs.length.should.equal(2);
|
||||||
outputs[0].satoshis.should.equal(1000);
|
outputs[0].satoshis.should.equal(1000);
|
||||||
@ -525,7 +618,7 @@ describe('AddressModule', function() {
|
|||||||
it('should handle an error from getOutputs', function(done) {
|
it('should handle an error from getOutputs', function(done) {
|
||||||
var am = new AddressModule({db: mockdb});
|
var am = new AddressModule({db: mockdb});
|
||||||
am.getOutputs = sinon.stub().callsArgWith(2, new Error('error'));
|
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);
|
should.exist(err);
|
||||||
err.message.should.equal('error');
|
err.message.should.equal('error');
|
||||||
done();
|
done();
|
||||||
@ -534,7 +627,7 @@ describe('AddressModule', function() {
|
|||||||
it('should handle when there are no outputs', function(done) {
|
it('should handle when there are no outputs', function(done) {
|
||||||
var am = new AddressModule({db: mockdb});
|
var am = new AddressModule({db: mockdb});
|
||||||
am.getOutputs = sinon.stub().callsArgWith(2, null, []);
|
am.getOutputs = sinon.stub().callsArgWith(2, null, []);
|
||||||
am.getUnspentOutputs('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) {
|
am.getUnspentOutputsForAddress('1KiW1A4dx1oRgLHtDtBjcunUGkYtFgZ1W', false, function(err, outputs) {
|
||||||
should.exist(err);
|
should.exist(err);
|
||||||
err.should.be.instanceof(errors.NoOutputs);
|
err.should.be.instanceof(errors.NoOutputs);
|
||||||
outputs.length.should.equal(0);
|
outputs.length.should.equal(0);
|
||||||
@ -628,14 +721,16 @@ describe('AddressModule', function() {
|
|||||||
inputIndex: 0,
|
inputIndex: 0,
|
||||||
height: 1,
|
height: 1,
|
||||||
timestamp: 1438289011844,
|
timestamp: 1438289011844,
|
||||||
satoshis: 5000
|
satoshis: 5000,
|
||||||
|
getFee: sinon.stub().returns(1000)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
txid: 'tx3',
|
txid: 'tx3',
|
||||||
outputIndex: 1,
|
outputIndex: 1,
|
||||||
height: 3,
|
height: 3,
|
||||||
timestamp: 1438289031844,
|
timestamp: 1438289031844,
|
||||||
satoshis: 2000
|
satoshis: 2000,
|
||||||
|
getFee: sinon.stub().returns(1000)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
txid: 'tx4',
|
txid: 'tx4',
|
||||||
@ -644,7 +739,8 @@ describe('AddressModule', function() {
|
|||||||
inputIndex: 1,
|
inputIndex: 1,
|
||||||
height: 4,
|
height: 4,
|
||||||
timestamp: 1438289041844,
|
timestamp: 1438289041844,
|
||||||
satoshis: 3000
|
satoshis: 3000,
|
||||||
|
getFee: sinon.stub().returns(1000)
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -659,7 +755,8 @@ describe('AddressModule', function() {
|
|||||||
satoshis: 5000
|
satoshis: 5000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
getFee: sinon.stub().returns(1000)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
txid: 'tx5',
|
txid: 'tx5',
|
||||||
@ -672,7 +769,8 @@ describe('AddressModule', function() {
|
|||||||
satoshis: 3000
|
satoshis: 3000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
getFee: sinon.stub().returns(1000)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -689,6 +787,7 @@ describe('AddressModule', function() {
|
|||||||
transaction.hash = txid;
|
transaction.hash = txid;
|
||||||
transaction.__height = incoming[i].height;
|
transaction.__height = incoming[i].height;
|
||||||
transaction.__timestamp = incoming[i].timestamp;
|
transaction.__timestamp = incoming[i].timestamp;
|
||||||
|
transaction.getFee = incoming[i].getFee;
|
||||||
return callback(null, transaction);
|
return callback(null, transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -702,6 +801,7 @@ describe('AddressModule', function() {
|
|||||||
transaction.__height = outgoing[i].height;
|
transaction.__height = outgoing[i].height;
|
||||||
transaction.__timestamp = outgoing[i].timestamp;
|
transaction.__timestamp = outgoing[i].timestamp;
|
||||||
transaction.inputs = outgoing[i].inputs;
|
transaction.inputs = outgoing[i].inputs;
|
||||||
|
transaction.getFee = outgoing[i].getFee;
|
||||||
return callback(null, transaction);
|
return callback(null, transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -709,6 +809,11 @@ describe('AddressModule', function() {
|
|||||||
},
|
},
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
on: sinon.stub()
|
on: sinon.stub()
|
||||||
|
},
|
||||||
|
chain: {
|
||||||
|
tip: {
|
||||||
|
__height: 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var am = new AddressModule({db: db});
|
var am = new AddressModule({db: db});
|
||||||
@ -733,26 +838,31 @@ describe('AddressModule', function() {
|
|||||||
it('should give transaction history for an address', function(done) {
|
it('should give transaction history for an address', function(done) {
|
||||||
am.getAddressHistory('address', true, function(err, history) {
|
am.getAddressHistory('address', true, function(err, history) {
|
||||||
should.not.exist(err);
|
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].satoshis.should.equal(5000);
|
||||||
history[0].height.should.equal(1);
|
history[0].height.should.equal(1);
|
||||||
history[0].timestamp.should.equal(1438289011844);
|
history[0].timestamp.should.equal(1438289011844);
|
||||||
history[1].transaction.hash.should.equal('tx2');
|
history[0].fees.should.equal(1000);
|
||||||
|
history[1].tx.hash.should.equal('tx2');
|
||||||
history[1].satoshis.should.equal(-5000);
|
history[1].satoshis.should.equal(-5000);
|
||||||
history[1].height.should.equal(2);
|
history[1].height.should.equal(2);
|
||||||
history[1].timestamp.should.equal(1438289021844);
|
history[1].timestamp.should.equal(1438289021844);
|
||||||
history[2].transaction.hash.should.equal('tx3');
|
history[1].fees.should.equal(1000);
|
||||||
|
history[2].tx.hash.should.equal('tx3');
|
||||||
history[2].satoshis.should.equal(2000);
|
history[2].satoshis.should.equal(2000);
|
||||||
history[2].height.should.equal(3);
|
history[2].height.should.equal(3);
|
||||||
history[2].timestamp.should.equal(1438289031844);
|
history[2].timestamp.should.equal(1438289031844);
|
||||||
history[3].transaction.hash.should.equal('tx4');
|
history[2].fees.should.equal(1000);
|
||||||
|
history[3].tx.hash.should.equal('tx4');
|
||||||
history[3].satoshis.should.equal(3000);
|
history[3].satoshis.should.equal(3000);
|
||||||
history[3].height.should.equal(4);
|
history[3].height.should.equal(4);
|
||||||
history[3].timestamp.should.equal(1438289041844);
|
history[3].timestamp.should.equal(1438289041844);
|
||||||
history[4].transaction.hash.should.equal('tx5');
|
history[3].fees.should.equal(1000);
|
||||||
|
history[4].tx.hash.should.equal('tx5');
|
||||||
history[4].satoshis.should.equal(-3000);
|
history[4].satoshis.should.equal(-3000);
|
||||||
history[4].height.should.equal(5);
|
history[4].height.should.equal(5);
|
||||||
history[4].timestamp.should.equal(1438289051844);
|
history[4].timestamp.should.equal(1438289051844);
|
||||||
|
history[4].fees.should.equal(1000);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -71,6 +71,7 @@ describe('Bitcoind Node', function() {
|
|||||||
it('should return modules publish events', function() {
|
it('should return modules publish events', function() {
|
||||||
var node = new Node({});
|
var node = new Node({});
|
||||||
var db = {
|
var db = {
|
||||||
|
getPublishEvents: sinon.stub().returns(['db1', 'db2']),
|
||||||
modules: [
|
modules: [
|
||||||
{
|
{
|
||||||
getPublishEvents: sinon.stub().returns(['mda1', 'mda2'])
|
getPublishEvents: sinon.stub().returns(['mda1', 'mda2'])
|
||||||
@ -83,7 +84,7 @@ describe('Bitcoind Node', function() {
|
|||||||
node.db = db;
|
node.db = db;
|
||||||
|
|
||||||
var events = node.getAllPublishEvents();
|
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() {
|
describe('#_loadConfiguration', function() {
|
||||||
@ -462,7 +463,6 @@ describe('Bitcoind Node', function() {
|
|||||||
setImmediate(function() {
|
setImmediate(function() {
|
||||||
chainlib.log.info.callCount.should.equal(1);
|
chainlib.log.info.callCount.should.equal(1);
|
||||||
chainlib.log.info.restore();
|
chainlib.log.info.restore();
|
||||||
node.db.addModule.callCount.should.equal(1);
|
|
||||||
node.chain.initialize.callCount.should.equal(1);
|
node.chain.initialize.callCount.should.equal(1);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user