Merge pull request #111 from pnagurny/feature/changes-for-bws

Changes for integrating into BWS
This commit is contained in:
Braydon Fuller 2015-08-19 15:15:56 -04:00
commit 96824feeaa
15 changed files with 554 additions and 143 deletions

View File

@ -77,11 +77,7 @@ node.on('ready', function() {
}
if(result) {
if(result.toJSON) {
response.result = result.toJSON();
} else {
response.result = result;
}
response.result = result;
}
socketCallback(response);
@ -114,11 +110,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(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

@ -48,8 +48,9 @@ Block.fromBufferReader = function(br) {
return new Block(obj);
};
Block.prototype.toObject = function() {
Block.prototype.toObject = Block.prototype.toJSON = function() {
return {
hash: this.hash,
version: this.version,
prevHash: this.prevHash,
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) {
/* 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,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) {
// bitcoind does the validation
setImmediate(callback);
@ -195,6 +232,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.block[i].emit('block', block.hash);
}
async.eachSeries(
this.modules,
function(module, next) {
@ -222,7 +264,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 +276,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 +301,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,16 +45,16 @@ 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 = {};
@ -447,13 +496,21 @@ AddressModule.prototype.getAddressHistory = function(address, queryMempool, call
return callback(err);
}
var confirmations = 0;
if(transaction.__height >= 0) {
confirmations = self.db.chain.tip.__height - transaction.__height;
}
txinfos[transaction.hash] = {
address: address,
satoshis: 0,
height: transaction.__height,
confirmations: confirmations,
timestamp: transaction.__timestamp,
fees: transaction.getFee(),
outputIndexes: [],
inputIndexes: [],
transaction: transaction
tx: transaction
};
callback(null, txinfos[transaction.hash]);
@ -490,7 +547,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++) {

View File

@ -44,8 +44,8 @@
"dependencies": {
"async": "1.3.0",
"bindings": "^1.2.1",
"bitcore": "^0.12.15",
"chainlib": "^0.1.1",
"bitcore": "^0.13.0",
"chainlib": "^0.1.3",
"errno": "^0.1.2",
"memdown": "^1.0.0",
"mkdirp": "0.5.0",

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

@ -10,10 +10,25 @@ var errors = bitcoindjs.errors;
var memdown = require('memdown');
var inherits = require('util').inherits;
var BaseModule = require('../lib/module');
var bitcore = require('bitcore');
var Transaction = bitcore.Transaction;
describe('Bitcoin DB', function() {
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() {
it('will return a NotFound error', function(done) {
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() {
it('build genisis data', function() {
var db = new DB({path: 'path', store: memdown});
@ -301,7 +422,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 +446,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

@ -2,12 +2,12 @@
var should = require('chai').should();
var sinon = require('sinon');
var bitcoindjs = require('../../');
var AddressModule = bitcoindjs.modules.AddressModule;
var bitcorenode = require('../../');
var AddressModule = bitcorenode.modules.AddressModule;
var blockData = require('../data/livenet-345003.json');
var bitcore = require('bitcore');
var EventEmitter = require('events').EventEmitter;
var errors = bitcoindjs.errors;
var errors = bitcorenode.errors;
var chainlib = require('chainlib');
var levelup = chainlib.deps.levelup;
@ -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
}
}
};
@ -490,6 +495,94 @@ describe('AddressModule', 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) {
var outputs = [
{
@ -514,7 +607,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 +618,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 +627,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);
@ -628,14 +721,16 @@ describe('AddressModule', function() {
inputIndex: 0,
height: 1,
timestamp: 1438289011844,
satoshis: 5000
satoshis: 5000,
getFee: sinon.stub().returns(1000)
},
{
txid: 'tx3',
outputIndex: 1,
height: 3,
timestamp: 1438289031844,
satoshis: 2000
satoshis: 2000,
getFee: sinon.stub().returns(1000)
},
{
txid: 'tx4',
@ -644,7 +739,8 @@ describe('AddressModule', function() {
inputIndex: 1,
height: 4,
timestamp: 1438289041844,
satoshis: 3000
satoshis: 3000,
getFee: sinon.stub().returns(1000)
},
];
@ -659,7 +755,8 @@ describe('AddressModule', function() {
satoshis: 5000
}
}
]
],
getFee: sinon.stub().returns(1000)
},
{
txid: 'tx5',
@ -672,7 +769,8 @@ describe('AddressModule', function() {
satoshis: 3000
}
}
]
],
getFee: sinon.stub().returns(1000)
}
];
@ -689,6 +787,7 @@ describe('AddressModule', function() {
transaction.hash = txid;
transaction.__height = incoming[i].height;
transaction.__timestamp = incoming[i].timestamp;
transaction.getFee = incoming[i].getFee;
return callback(null, transaction);
}
}
@ -702,6 +801,7 @@ describe('AddressModule', function() {
transaction.__height = outgoing[i].height;
transaction.__timestamp = outgoing[i].timestamp;
transaction.inputs = outgoing[i].inputs;
transaction.getFee = outgoing[i].getFee;
return callback(null, transaction);
}
}
@ -709,6 +809,11 @@ describe('AddressModule', function() {
},
bitcoind: {
on: sinon.stub()
},
chain: {
tip: {
__height: 1
}
}
};
var am = new AddressModule({db: db});
@ -733,26 +838,31 @@ 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[0].fees.should.equal(1000);
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[1].fees.should.equal(1000);
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[2].fees.should.equal(1000);
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[3].fees.should.equal(1000);
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);
history[4].fees.should.equal(1000);
done();
});
});

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