flocore-node/test/services/bitcoind.unit.js
Braydon Fuller 814576953c bitcoind: relative spawn.datadir handling
Will expand the datadir into an absolute path based on the location
of the configuration file. This is to avoid unexpected behavior in regards
to the location of configuration files.
2016-06-01 11:33:06 -04:00

4779 lines
165 KiB
JavaScript

'use strict';
/* jshint sub: true */
var path = require('path');
var EventEmitter = require('events').EventEmitter;
var should = require('chai').should();
var crypto = require('crypto');
var bitcore = require('bitcore-lib');
var _ = bitcore.deps._;
var sinon = require('sinon');
var proxyquire = require('proxyquire');
var fs = require('fs');
var sinon = require('sinon');
var index = require('../../lib');
var log = index.log;
var errors = index.errors;
var Transaction = bitcore.Transaction;
var readFileSync = sinon.stub().returns(fs.readFileSync(path.resolve(__dirname, '../data/bitcoin.conf')));
var BitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFileSync: readFileSync
}
});
var defaultBitcoinConf = fs.readFileSync(path.resolve(__dirname, '../data/default.bitcoin.conf'), 'utf8');
describe('Bitcoin Service', function() {
var txhex = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000';
var baseConfig = {
node: {
network: bitcore.Networks.testnet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
describe('@constructor', function() {
it('will create an instance', function() {
var bitcoind = new BitcoinService(baseConfig);
should.exist(bitcoind);
});
it('will create an instance without `new`', function() {
var bitcoind = BitcoinService(baseConfig);
should.exist(bitcoind);
});
it('will init caches', function() {
var bitcoind = new BitcoinService(baseConfig);
should.exist(bitcoind.utxosCache);
should.exist(bitcoind.txidsCache);
should.exist(bitcoind.balanceCache);
should.exist(bitcoind.summaryCache);
should.exist(bitcoind.transactionDetailedCache);
should.exist(bitcoind.transactionCache);
should.exist(bitcoind.rawTransactionCache);
should.exist(bitcoind.blockCache);
should.exist(bitcoind.rawBlockCache);
should.exist(bitcoind.blockHeaderCache);
should.exist(bitcoind.zmqKnownTransactions);
should.exist(bitcoind.zmqKnownBlocks);
should.exist(bitcoind.lastTip);
should.exist(bitcoind.lastTipTimeout);
});
it('will init clients', function() {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.should.deep.equal([]);
bitcoind.nodesIndex.should.equal(0);
bitcoind.nodes.push({client: sinon.stub()});
should.exist(bitcoind.client);
});
it('will set subscriptions', function() {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.subscriptions.should.deep.equal({
address: {},
rawtransaction: [],
hashblock: []
});
});
});
describe('@dependencies', function() {
it('will have no dependencies', function() {
BitcoinService.dependencies.should.deep.equal([]);
});
});
describe('#getAPIMethods', function() {
it('will return spec', function() {
var bitcoind = new BitcoinService(baseConfig);
var methods = bitcoind.getAPIMethods();
should.exist(methods);
methods.length.should.equal(21);
});
});
describe('#getPublishEvents', function() {
it('will return spec', function() {
var bitcoind = new BitcoinService(baseConfig);
var events = bitcoind.getPublishEvents();
should.exist(events);
events.length.should.equal(3);
events[0].name.should.equal('bitcoind/rawtransaction');
events[0].scope.should.equal(bitcoind);
events[0].subscribe.should.be.a('function');
events[0].unsubscribe.should.be.a('function');
events[1].name.should.equal('bitcoind/hashblock');
events[1].scope.should.equal(bitcoind);
events[1].subscribe.should.be.a('function');
events[1].unsubscribe.should.be.a('function');
events[2].name.should.equal('bitcoind/addresstxid');
events[2].scope.should.equal(bitcoind);
events[2].subscribe.should.be.a('function');
events[2].unsubscribe.should.be.a('function');
});
it('will call subscribe/unsubscribe with correct args', function() {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.subscribe = sinon.stub();
bitcoind.unsubscribe = sinon.stub();
var events = bitcoind.getPublishEvents();
events[0].subscribe('test');
bitcoind.subscribe.args[0][0].should.equal('rawtransaction');
bitcoind.subscribe.args[0][1].should.equal('test');
events[0].unsubscribe('test');
bitcoind.unsubscribe.args[0][0].should.equal('rawtransaction');
bitcoind.unsubscribe.args[0][1].should.equal('test');
events[1].subscribe('test');
bitcoind.subscribe.args[1][0].should.equal('hashblock');
bitcoind.subscribe.args[1][1].should.equal('test');
events[1].unsubscribe('test');
bitcoind.unsubscribe.args[1][0].should.equal('hashblock');
bitcoind.unsubscribe.args[1][1].should.equal('test');
});
});
describe('#subscribe', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'info');
});
afterEach(function() {
sandbox.restore();
});
it('will push to subscriptions', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter = {};
bitcoind.subscribe('hashblock', emitter);
bitcoind.subscriptions.hashblock[0].should.equal(emitter);
var emitter2 = {};
bitcoind.subscribe('rawtransaction', emitter2);
bitcoind.subscriptions.rawtransaction[0].should.equal(emitter2);
});
});
describe('#unsubscribe', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'info');
});
afterEach(function() {
sandbox.restore();
});
it('will remove item from subscriptions', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter1 = {};
var emitter2 = {};
var emitter3 = {};
var emitter4 = {};
var emitter5 = {};
bitcoind.subscribe('hashblock', emitter1);
bitcoind.subscribe('hashblock', emitter2);
bitcoind.subscribe('hashblock', emitter3);
bitcoind.subscribe('hashblock', emitter4);
bitcoind.subscribe('hashblock', emitter5);
bitcoind.subscriptions.hashblock.length.should.equal(5);
bitcoind.unsubscribe('hashblock', emitter3);
bitcoind.subscriptions.hashblock.length.should.equal(4);
bitcoind.subscriptions.hashblock[0].should.equal(emitter1);
bitcoind.subscriptions.hashblock[1].should.equal(emitter2);
bitcoind.subscriptions.hashblock[2].should.equal(emitter4);
bitcoind.subscriptions.hashblock[3].should.equal(emitter5);
});
it('will not remove item an already unsubscribed item', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter1 = {};
var emitter3 = {};
bitcoind.subscriptions.hashblock= [emitter1];
bitcoind.unsubscribe('hashblock', emitter3);
bitcoind.subscriptions.hashblock.length.should.equal(1);
bitcoind.subscriptions.hashblock[0].should.equal(emitter1);
});
});
describe('#subscribeAddress', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'info');
});
afterEach(function() {
sandbox.restore();
});
it('will not an invalid address', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter = new EventEmitter();
bitcoind.subscribeAddress(emitter, ['invalidaddress']);
should.not.exist(bitcoind.subscriptions.address['invalidaddress']);
});
it('will add a valid address', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter = new EventEmitter();
bitcoind.subscribeAddress(emitter, ['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
should.exist(bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
});
it('will handle multiple address subscribers', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter1 = new EventEmitter();
var emitter2 = new EventEmitter();
bitcoind.subscribeAddress(emitter1, ['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
bitcoind.subscribeAddress(emitter2, ['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
should.exist(bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'].length.should.equal(2);
});
it('will not add the same emitter twice', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter1 = new EventEmitter();
bitcoind.subscribeAddress(emitter1, ['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
bitcoind.subscribeAddress(emitter1, ['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
should.exist(bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'].length.should.equal(1);
});
});
describe('#unsubscribeAddress', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'info');
});
afterEach(function() {
sandbox.restore();
});
it('it will remove a subscription', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter1 = new EventEmitter();
var emitter2 = new EventEmitter();
bitcoind.subscribeAddress(emitter1, ['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
bitcoind.subscribeAddress(emitter2, ['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
should.exist(bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'].length.should.equal(2);
bitcoind.unsubscribeAddress(emitter1, ['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'].length.should.equal(1);
});
it('will unsubscribe subscriptions for an emitter', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter1 = new EventEmitter();
var emitter2 = new EventEmitter();
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'] = [emitter1, emitter2];
bitcoind.unsubscribeAddress(emitter1);
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'].length.should.equal(1);
});
it('will NOT unsubscribe subscription with missing address', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter1 = new EventEmitter();
var emitter2 = new EventEmitter();
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'] = [emitter1, emitter2];
bitcoind.unsubscribeAddress(emitter1, ['1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo']);
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'].length.should.equal(2);
});
it('will NOT unsubscribe subscription with missing emitter', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter1 = new EventEmitter();
var emitter2 = new EventEmitter();
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'] = [emitter2];
bitcoind.unsubscribeAddress(emitter1, ['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'].length.should.equal(1);
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'][0].should.equal(emitter2);
});
it('will remove empty addresses', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter1 = new EventEmitter();
var emitter2 = new EventEmitter();
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'] = [emitter1, emitter2];
bitcoind.unsubscribeAddress(emitter1, ['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
bitcoind.unsubscribeAddress(emitter2, ['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
should.not.exist(bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br']);
});
it('will unsubscribe emitter for all addresses', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter1 = new EventEmitter();
var emitter2 = new EventEmitter();
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'] = [emitter1, emitter2];
bitcoind.subscriptions.address['1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo'] = [emitter1, emitter2];
sinon.spy(bitcoind, 'unsubscribeAddressAll');
bitcoind.unsubscribeAddress(emitter1);
bitcoind.unsubscribeAddressAll.callCount.should.equal(1);
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'].length.should.equal(1);
bitcoind.subscriptions.address['1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo'].length.should.equal(1);
});
});
describe('#unsubscribeAddressAll', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'info');
});
afterEach(function() {
sandbox.restore();
});
it('will unsubscribe emitter for all addresses', function() {
var bitcoind = new BitcoinService(baseConfig);
var emitter1 = new EventEmitter();
var emitter2 = new EventEmitter();
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'] = [emitter1, emitter2];
bitcoind.subscriptions.address['1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo'] = [emitter1, emitter2];
bitcoind.subscriptions.address['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'] = [emitter2];
bitcoind.subscriptions.address['3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou'] = [emitter1];
bitcoind.unsubscribeAddress(emitter1);
bitcoind.subscriptions.address['2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br'].length.should.equal(1);
bitcoind.subscriptions.address['1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo'].length.should.equal(1);
bitcoind.subscriptions.address['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'].length.should.equal(1);
should.not.exist(bitcoind.subscriptions.address['3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou']);
});
});
describe('#_getDefaultConfig', function() {
it('will generate config file from defaults', function() {
var bitcoind = new BitcoinService(baseConfig);
var config = bitcoind._getDefaultConfig();
config.should.equal(defaultBitcoinConf);
});
});
describe('#_loadSpawnConfiguration', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'info');
});
afterEach(function() {
sandbox.restore();
});
it('will parse a bitcoin.conf file', function() {
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
fs: {
readFileSync: readFileSync,
existsSync: sinon.stub().returns(true),
writeFileSync: sinon.stub()
},
mkdirp: {
sync: sinon.stub()
}
});
var bitcoind = new TestBitcoin(baseConfig);
bitcoind.options.spawn.datadir = '/tmp/.bitcoin';
var node = {};
bitcoind._loadSpawnConfiguration(node);
should.exist(bitcoind.spawn.config);
bitcoind.spawn.config.should.deep.equal({
addressindex: 1,
checkblocks: 144,
dbcache: 8192,
maxuploadtarget: 1024,
port: 20000,
rpcport: 50001,
rpcallowip: '127.0.0.1',
rpcuser: 'bitcoin',
rpcpassword: 'local321',
server: 1,
spentindex: 1,
timestampindex: 1,
txindex: 1,
upnp: 0,
whitelist: '127.0.0.1',
zmqpubhashblock: 'tcp://127.0.0.1:28332',
zmqpubrawtx: 'tcp://127.0.0.1:28332'
});
});
it('will expand relative datadir to absolute path', function() {
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
fs: {
readFileSync: readFileSync,
existsSync: sinon.stub().returns(true),
writeFileSync: sinon.stub()
},
mkdirp: {
sync: sinon.stub()
}
});
var config = {
node: {
network: bitcore.Networks.testnet,
configPath: '/tmp/.bitcore/bitcore-node.json'
},
spawn: {
datadir: './data',
exec: 'testpath'
}
};
var bitcoind = new TestBitcoin(config);
bitcoind.options.spawn.datadir = './data';
var node = {};
bitcoind._loadSpawnConfiguration(node);
bitcoind.options.spawn.datadir.should.equal('/tmp/.bitcore/data');
});
it('should throw an exception if txindex isn\'t enabled in the configuration', function() {
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
fs: {
readFileSync: sinon.stub().returns(fs.readFileSync(__dirname + '/../data/badbitcoin.conf')),
existsSync: sinon.stub().returns(true),
},
mkdirp: {
sync: sinon.stub()
}
});
var bitcoind = new TestBitcoin(baseConfig);
(function() {
bitcoind._loadSpawnConfiguration({datadir: './test'});
}).should.throw(bitcore.errors.InvalidState);
});
it('should NOT set https options if node https options are set', function() {
var writeFileSync = function(path, config) {
config.should.equal(defaultBitcoinConf);
};
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
fs: {
writeFileSync: writeFileSync,
readFileSync: readFileSync,
existsSync: sinon.stub().returns(false)
},
mkdirp: {
sync: sinon.stub()
}
});
var config = {
node: {
network: {
name: 'regtest'
},
https: true,
httpsOptions: {
key: 'key.pem',
cert: 'cert.pem'
}
},
spawn: {
datadir: 'testdir',
exec: 'testexec'
}
};
var bitcoind = new TestBitcoin(config);
bitcoind.options.spawn.datadir = '/tmp/.bitcoin';
var node = {};
bitcoind._loadSpawnConfiguration(node);
});
});
describe('#_checkConfigIndexes', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'warn');
});
after(function() {
sandbox.restore();
});
it('should warn the user if reindex is set to 1 in the bitcoin.conf file', function() {
var bitcoind = new BitcoinService(baseConfig);
var config = {
txindex: 1,
addressindex: 1,
spentindex: 1,
server: 1,
zmqpubrawtx: 1,
zmqpubhashblock: 1,
reindex: 1
};
var node = {};
bitcoind._checkConfigIndexes(config, node);
log.warn.callCount.should.equal(1);
node._reindex.should.equal(true);
});
});
describe('#_resetCaches', function() {
it('will reset LRU caches', function() {
var bitcoind = new BitcoinService(baseConfig);
var keys = [];
for (var i = 0; i < 10; i++) {
keys.push(crypto.randomBytes(32));
bitcoind.transactionDetailedCache.set(keys[i], {});
bitcoind.utxosCache.set(keys[i], {});
bitcoind.txidsCache.set(keys[i], {});
bitcoind.balanceCache.set(keys[i], {});
bitcoind.summaryCache.set(keys[i], {});
}
bitcoind._resetCaches();
should.equal(bitcoind.transactionDetailedCache.get(keys[0]), undefined);
should.equal(bitcoind.utxosCache.get(keys[0]), undefined);
should.equal(bitcoind.txidsCache.get(keys[0]), undefined);
should.equal(bitcoind.balanceCache.get(keys[0]), undefined);
should.equal(bitcoind.summaryCache.get(keys[0]), undefined);
});
});
describe('#_tryAll', function() {
it('will retry the number of bitcoind nodes', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.tryAllInterval = 1;
bitcoind.nodes.push({});
bitcoind.nodes.push({});
bitcoind.nodes.push({});
var count = 0;
var func = function(callback) {
count++;
if (count <= 2) {
callback(new Error('test'));
} else {
callback();
}
};
bitcoind._tryAll(function(next) {
func(next);
}, function() {
count.should.equal(3);
done();
});
});
it('will get error if all fail', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.tryAllInterval = 1;
bitcoind.nodes.push({});
bitcoind.nodes.push({});
bitcoind.nodes.push({});
var count = 0;
var func = function(callback) {
count++;
callback(new Error('test'));
};
bitcoind._tryAll(function(next) {
func(next);
}, function(err) {
should.exist(err);
err.message.should.equal('test');
count.should.equal(3);
done();
});
});
});
describe('#_wrapRPCError', function() {
it('will convert bitcoind-rpc error object into JavaScript error', function() {
var bitcoind = new BitcoinService(baseConfig);
var error = bitcoind._wrapRPCError({message: 'Test error', code: -1});
error.should.be.an.instanceof(errors.RPCError);
error.code.should.equal(-1);
error.message.should.equal('Test error');
});
});
describe('#_initChain', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'info');
});
afterEach(function() {
sandbox.restore();
});
it('will set height and genesis buffer', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var genesisBuffer = new Buffer([]);
bitcoind.getRawBlock = sinon.stub().callsArgWith(1, null, genesisBuffer);
bitcoind.nodes.push({
client: {
getBestBlockHash: function(callback) {
callback(null, {
result: 'bestblockhash'
});
},
getBlock: function(hash, callback) {
if (hash === 'bestblockhash') {
callback(null, {
result: {
height: 5000
}
});
}
},
getBlockHash: function(num, callback) {
callback(null, {
result: 'genesishash'
});
}
}
});
bitcoind._initChain(function() {
log.info.callCount.should.equal(1);
bitcoind.getRawBlock.callCount.should.equal(1);
bitcoind.getRawBlock.args[0][0].should.equal('genesishash');
bitcoind.height.should.equal(5000);
bitcoind.genesisBuffer.should.equal(genesisBuffer);
done();
});
});
it('it will handle error from getBestBlockHash', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBestBlockHash = sinon.stub().callsArgWith(0, {code: -1, message: 'error'});
bitcoind.nodes.push({
client: {
getBestBlockHash: getBestBlockHash
}
});
bitcoind._initChain(function(err) {
err.should.be.instanceOf(Error);
done();
});
});
it('it will handle error from getBlock', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBestBlockHash = sinon.stub().callsArgWith(0, null, {});
var getBlock = sinon.stub().callsArgWith(1, {code: -1, message: 'error'});
bitcoind.nodes.push({
client: {
getBestBlockHash: getBestBlockHash,
getBlock: getBlock
}
});
bitcoind._initChain(function(err) {
err.should.be.instanceOf(Error);
done();
});
});
it('it will handle error from getBlockHash', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBestBlockHash = sinon.stub().callsArgWith(0, null, {});
var getBlock = sinon.stub().callsArgWith(1, null, {
result: {
height: 10
}
});
var getBlockHash = sinon.stub().callsArgWith(1, {code: -1, message: 'error'});
bitcoind.nodes.push({
client: {
getBestBlockHash: getBestBlockHash,
getBlock: getBlock,
getBlockHash: getBlockHash
}
});
bitcoind._initChain(function(err) {
err.should.be.instanceOf(Error);
done();
});
});
it('it will handle error from getRawBlock', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBestBlockHash = sinon.stub().callsArgWith(0, null, {});
var getBlock = sinon.stub().callsArgWith(1, null, {
result: {
height: 10
}
});
var getBlockHash = sinon.stub().callsArgWith(1, null, {});
bitcoind.nodes.push({
client: {
getBestBlockHash: getBestBlockHash,
getBlock: getBlock,
getBlockHash: getBlockHash
}
});
bitcoind.getRawBlock = sinon.stub().callsArgWith(1, new Error('test'));
bitcoind._initChain(function(err) {
err.should.be.instanceOf(Error);
done();
});
});
});
describe('#_getDefaultConf', function() {
afterEach(function() {
bitcore.Networks.disableRegtest();
baseConfig.node.network = bitcore.Networks.testnet;
});
it('will get default rpc port for livenet', function() {
var config = {
node: {
network: bitcore.Networks.livenet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
var bitcoind = new BitcoinService(config);
bitcoind._getDefaultConf().rpcport.should.equal(8332);
});
it('will get default rpc port for testnet', function() {
var config = {
node: {
network: bitcore.Networks.testnet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
var bitcoind = new BitcoinService(config);
bitcoind._getDefaultConf().rpcport.should.equal(18332);
});
it('will get default rpc port for regtest', function() {
bitcore.Networks.enableRegtest();
var config = {
node: {
network: bitcore.Networks.testnet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
var bitcoind = new BitcoinService(config);
bitcoind._getDefaultConf().rpcport.should.equal(18332);
});
});
describe('#_getNetworkConfigPath', function() {
afterEach(function() {
bitcore.Networks.disableRegtest();
baseConfig.node.network = bitcore.Networks.testnet;
});
it('will get default config path for livenet', function() {
var config = {
node: {
network: bitcore.Networks.livenet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
var bitcoind = new BitcoinService(config);
should.equal(bitcoind._getNetworkConfigPath(), undefined);
});
it('will get default rpc port for testnet', function() {
var config = {
node: {
network: bitcore.Networks.testnet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
var bitcoind = new BitcoinService(config);
bitcoind._getNetworkConfigPath().should.equal('testnet3/bitcoin.conf');
});
it('will get default rpc port for regtest', function() {
bitcore.Networks.enableRegtest();
var config = {
node: {
network: bitcore.Networks.testnet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
var bitcoind = new BitcoinService(config);
bitcoind._getNetworkConfigPath().should.equal('regtest/bitcoin.conf');
});
});
describe('#_getNetworkOption', function() {
afterEach(function() {
bitcore.Networks.disableRegtest();
baseConfig.node.network = bitcore.Networks.testnet;
});
it('return --testnet for testnet', function() {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.node.network = bitcore.Networks.testnet;
bitcoind._getNetworkOption().should.equal('--testnet');
});
it('return --regtest for testnet', function() {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.node.network = bitcore.Networks.testnet;
bitcore.Networks.enableRegtest();
bitcoind._getNetworkOption().should.equal('--regtest');
});
it('return undefined for livenet', function() {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.node.network = bitcore.Networks.livenet;
bitcore.Networks.enableRegtest();
should.equal(bitcoind._getNetworkOption(), undefined);
});
});
describe('#_zmqBlockHandler', function() {
it('will emit block', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var node = {};
var message = new Buffer('00000000002e08fc7ae9a9aa5380e95e2adcdc5752a4a66a7d3a22466bd4e6aa', 'hex');
bitcoind._rapidProtectedUpdateTip = sinon.stub();
bitcoind.on('block', function(block) {
block.should.equal(message);
done();
});
bitcoind._zmqBlockHandler(node, message);
});
it('will not emit same block twice', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var node = {};
var message = new Buffer('00000000002e08fc7ae9a9aa5380e95e2adcdc5752a4a66a7d3a22466bd4e6aa', 'hex');
bitcoind._rapidProtectedUpdateTip = sinon.stub();
bitcoind.on('block', function(block) {
block.should.equal(message);
done();
});
bitcoind._zmqBlockHandler(node, message);
bitcoind._zmqBlockHandler(node, message);
});
it('will call function to update tip', function() {
var bitcoind = new BitcoinService(baseConfig);
var node = {};
var message = new Buffer('00000000002e08fc7ae9a9aa5380e95e2adcdc5752a4a66a7d3a22466bd4e6aa', 'hex');
bitcoind._rapidProtectedUpdateTip = sinon.stub();
bitcoind._zmqBlockHandler(node, message);
bitcoind._rapidProtectedUpdateTip.callCount.should.equal(1);
bitcoind._rapidProtectedUpdateTip.args[0][0].should.equal(node);
bitcoind._rapidProtectedUpdateTip.args[0][1].should.equal(message);
});
it('will emit to subscribers', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var node = {};
var message = new Buffer('00000000002e08fc7ae9a9aa5380e95e2adcdc5752a4a66a7d3a22466bd4e6aa', 'hex');
bitcoind._rapidProtectedUpdateTip = sinon.stub();
var emitter = new EventEmitter();
bitcoind.subscriptions.hashblock.push(emitter);
emitter.on('bitcoind/hashblock', function(blockHash) {
blockHash.should.equal(message.toString('hex'));
done();
});
bitcoind._zmqBlockHandler(node, message);
});
});
describe('#_rapidProtectedUpdateTip', function() {
it('will limit tip updates with rapid calls', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var callCount = 0;
bitcoind._updateTip = function() {
callCount++;
callCount.should.be.within(1, 2);
if (callCount > 1) {
done();
}
};
var node = {};
var message = new Buffer('00000000002e08fc7ae9a9aa5380e95e2adcdc5752a4a66a7d3a22466bd4e6aa', 'hex');
var count = 0;
function repeat() {
bitcoind._rapidProtectedUpdateTip(node, message);
count++;
if (count < 50) {
repeat();
}
}
repeat();
});
});
describe('#_updateTip', function() {
var sandbox = sinon.sandbox.create();
var message = new Buffer('00000000002e08fc7ae9a9aa5380e95e2adcdc5752a4a66a7d3a22466bd4e6aa', 'hex');
beforeEach(function() {
sandbox.stub(log, 'error');
sandbox.stub(log, 'info');
});
afterEach(function() {
sandbox.restore();
});
it('log and emit rpc error from get block', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.syncPercentage = sinon.stub();
bitcoind.on('error', function(err) {
err.code.should.equal(-1);
err.message.should.equal('Test error');
log.error.callCount.should.equal(1);
done();
});
var node = {
client: {
getBlock: sinon.stub().callsArgWith(1, {message: 'Test error', code: -1})
}
};
bitcoind._updateTip(node, message);
});
it('emit synced if percentage is 100', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.syncPercentage = sinon.stub().callsArgWith(0, null, 100);
bitcoind.on('synced', function() {
done();
});
var node = {
client: {
getBlock: sinon.stub()
}
};
bitcoind._updateTip(node, message);
});
it('NOT emit synced if percentage is less than 100', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.syncPercentage = sinon.stub().callsArgWith(0, null, 99);
bitcoind.on('synced', function() {
throw new Error('Synced called');
});
var node = {
client: {
getBlock: sinon.stub()
}
};
bitcoind._updateTip(node, message);
log.info.callCount.should.equal(1);
done();
});
it('log and emit error from syncPercentage', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.syncPercentage = sinon.stub().callsArgWith(0, new Error('test'));
bitcoind.on('error', function(err) {
log.error.callCount.should.equal(1);
err.message.should.equal('test');
done();
});
var node = {
client: {
getBlock: sinon.stub()
}
};
bitcoind._updateTip(node, message);
});
it('reset caches and set height', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.syncPercentage = sinon.stub();
bitcoind._resetCaches = sinon.stub();
bitcoind.on('tip', function(height) {
bitcoind._resetCaches.callCount.should.equal(1);
height.should.equal(10);
bitcoind.height.should.equal(10);
done();
});
var node = {
client: {
getBlock: sinon.stub().callsArgWith(1, null, {
result: {
height: 10
}
})
}
};
bitcoind._updateTip(node, message);
});
it('will NOT update twice for the same hash', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.syncPercentage = sinon.stub();
bitcoind._resetCaches = sinon.stub();
bitcoind.on('tip', function() {
done();
});
var node = {
client: {
getBlock: sinon.stub().callsArgWith(1, null, {
result: {
height: 10
}
})
}
};
bitcoind._updateTip(node, message);
bitcoind._updateTip(node, message);
});
it('will not call syncPercentage if node is stopping', function(done) {
var config = {
node: {
network: bitcore.Networks.testnet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
var bitcoind = new BitcoinService(config);
bitcoind.syncPercentage = sinon.stub();
bitcoind._resetCaches = sinon.stub();
bitcoind.node.stopping = true;
var node = {
client: {
getBlock: sinon.stub().callsArgWith(1, null, {
result: {
height: 10
}
})
}
};
bitcoind.on('tip', function() {
bitcoind.syncPercentage.callCount.should.equal(0);
done();
});
bitcoind._updateTip(node, message);
});
});
describe('#_getAddressesFromTransaction', function() {
it('will get results using bitcore.Transaction', function() {
var bitcoind = new BitcoinService(baseConfig);
var wif = 'L2Gkw3kKJ6N24QcDuH4XDqt9cTqsKTVNDGz1CRZhk9cq4auDUbJy';
var privkey = bitcore.PrivateKey.fromWIF(wif);
var inputAddress = privkey.toAddress(bitcore.Networks.testnet);
var outputAddress = bitcore.Address('2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br');
var tx = bitcore.Transaction();
tx.from({
txid: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
outputIndex: 0,
script: bitcore.Script(inputAddress),
address: inputAddress.toString(),
satoshis: 5000000000
});
tx.to(outputAddress, 5000000000);
tx.sign(privkey);
var addresses = bitcoind._getAddressesFromTransaction(tx);
addresses.length.should.equal(2);
addresses[0].should.equal(inputAddress.toString());
addresses[1].should.equal(outputAddress.toString());
});
it('will handle non-standard script types', function() {
var bitcoind = new BitcoinService(baseConfig);
var tx = bitcore.Transaction();
tx.addInput(bitcore.Transaction.Input({
prevTxId: '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
script: bitcore.Script('OP_TRUE'),
outputIndex: 1,
output: {
script: bitcore.Script('OP_TRUE'),
satoshis: 5000000000
}
}));
tx.addOutput(bitcore.Transaction.Output({
script: bitcore.Script('OP_TRUE'),
satoshis: 5000000000
}));
var addresses = bitcoind._getAddressesFromTransaction(tx);
addresses.length.should.equal(0);
});
it('will handle unparsable script types or missing input script', function() {
var bitcoind = new BitcoinService(baseConfig);
var tx = bitcore.Transaction();
tx.addOutput(bitcore.Transaction.Output({
script: new Buffer('4c', 'hex'),
satoshis: 5000000000
}));
var addresses = bitcoind._getAddressesFromTransaction(tx);
addresses.length.should.equal(0);
});
it('will return unique values', function() {
var bitcoind = new BitcoinService(baseConfig);
var tx = bitcore.Transaction();
var address = bitcore.Address('2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br');
tx.addOutput(bitcore.Transaction.Output({
script: bitcore.Script(address),
satoshis: 5000000000
}));
tx.addOutput(bitcore.Transaction.Output({
script: bitcore.Script(address),
satoshis: 5000000000
}));
var addresses = bitcoind._getAddressesFromTransaction(tx);
addresses.length.should.equal(1);
});
});
describe('#_notifyAddressTxidSubscribers', function() {
it('will emit event if matching addresses', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind._getAddressesFromTransaction = sinon.stub().returns([address]);
var emitter = new EventEmitter();
bitcoind.subscriptions.address[address] = [emitter];
var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
var transaction = {};
emitter.on('bitcoind/addresstxid', function(data) {
data.address.should.equal(address);
data.txid.should.equal(txid);
done();
});
sinon.spy(emitter, 'emit');
bitcoind._notifyAddressTxidSubscribers(txid, transaction);
emitter.emit.callCount.should.equal(1);
});
it('will NOT emit event without matching addresses', function() {
var bitcoind = new BitcoinService(baseConfig);
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind._getAddressesFromTransaction = sinon.stub().returns([address]);
var emitter = new EventEmitter();
var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
var transaction = {};
emitter.emit = sinon.stub();
bitcoind._notifyAddressTxidSubscribers(txid, transaction);
emitter.emit.callCount.should.equal(0);
});
});
describe('#_zmqTransactionHandler', function() {
it('will emit to subscribers', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var expectedBuffer = new Buffer(txhex, 'hex');
var emitter = new EventEmitter();
bitcoind.subscriptions.rawtransaction.push(emitter);
emitter.on('bitcoind/rawtransaction', function(hex) {
hex.should.be.a('string');
hex.should.equal(expectedBuffer.toString('hex'));
done();
});
var node = {};
bitcoind._zmqTransactionHandler(node, expectedBuffer);
});
it('will NOT emit to subscribers more than once for the same tx', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var expectedBuffer = new Buffer(txhex, 'hex');
var emitter = new EventEmitter();
bitcoind.subscriptions.rawtransaction.push(emitter);
emitter.on('bitcoind/rawtransaction', function() {
done();
});
var node = {};
bitcoind._zmqTransactionHandler(node, expectedBuffer);
bitcoind._zmqTransactionHandler(node, expectedBuffer);
});
it('will emit "tx" event', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var expectedBuffer = new Buffer(txhex, 'hex');
bitcoind.on('tx', function(buffer) {
buffer.should.be.instanceof(Buffer);
buffer.toString('hex').should.equal(expectedBuffer.toString('hex'));
done();
});
var node = {};
bitcoind._zmqTransactionHandler(node, expectedBuffer);
});
it('will NOT emit "tx" event more than once for the same tx', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var expectedBuffer = new Buffer(txhex, 'hex');
bitcoind.on('tx', function() {
done();
});
var node = {};
bitcoind._zmqTransactionHandler(node, expectedBuffer);
bitcoind._zmqTransactionHandler(node, expectedBuffer);
});
});
describe('#_checkSyncedAndSubscribeZmqEvents', function() {
var sandbox = sinon.sandbox.create();
before(function() {
sandbox.stub(log, 'error');
});
after(function() {
sandbox.restore();
});
it('log errors, update tip and subscribe to zmq events', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._updateTip = sinon.stub();
bitcoind._subscribeZmqEvents = sinon.stub();
var blockEvents = 0;
bitcoind.on('block', function() {
blockEvents++;
});
var getBestBlockHash = sinon.stub().callsArgWith(0, null, {
result: '00000000000000001bb82a7f5973618cfd3185ba1ded04dd852a653f92a27c45'
});
getBestBlockHash.onCall(0).callsArgWith(0, {code: -1 , message: 'Test error'});
var progress = 0.90;
function getProgress() {
progress = progress + 0.01;
return progress;
}
var info = {};
Object.defineProperty(info, 'result', {
get: function() {
return {
verificationprogress: getProgress()
};
}
});
var getBlockchainInfo = sinon.stub().callsArgWith(0, null, info);
getBlockchainInfo.onCall(0).callsArgWith(0, {code: -1, message: 'Test error'});
var node = {
_reindex: true,
_reindexWait: 1,
_tipUpdateInterval: 1,
client: {
getBestBlockHash: getBestBlockHash,
getBlockchainInfo: getBlockchainInfo
}
};
bitcoind._checkSyncedAndSubscribeZmqEvents(node);
setTimeout(function() {
log.error.callCount.should.equal(2);
blockEvents.should.equal(11);
bitcoind._updateTip.callCount.should.equal(11);
bitcoind._subscribeZmqEvents.callCount.should.equal(1);
done();
}, 200);
});
it('it will clear interval if node is stopping', function(done) {
var config = {
node: {
network: bitcore.Networks.testnet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
var bitcoind = new BitcoinService(config);
var getBestBlockHash = sinon.stub().callsArgWith(0, {code: -1, message: 'error'});
var node = {
_tipUpdateInterval: 1,
client: {
getBestBlockHash: getBestBlockHash
}
};
bitcoind._checkSyncedAndSubscribeZmqEvents(node);
setTimeout(function() {
bitcoind.node.stopping = true;
var count = getBestBlockHash.callCount;
setTimeout(function() {
getBestBlockHash.callCount.should.equal(count);
done();
}, 100);
}, 100);
});
it('will not set interval if synced is true', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._updateTip = sinon.stub();
bitcoind._subscribeZmqEvents = sinon.stub();
var getBestBlockHash = sinon.stub().callsArgWith(0, null, {
result: '00000000000000001bb82a7f5973618cfd3185ba1ded04dd852a653f92a27c45'
});
var info = {
result: {
verificationprogress: 1.00
}
};
var getBlockchainInfo = sinon.stub().callsArgWith(0, null, info);
var node = {
_tipUpdateInterval: 1,
client: {
getBestBlockHash: getBestBlockHash,
getBlockchainInfo: getBlockchainInfo
}
};
bitcoind._checkSyncedAndSubscribeZmqEvents(node);
setTimeout(function() {
getBestBlockHash.callCount.should.equal(1);
getBlockchainInfo.callCount.should.equal(1);
done();
}, 200);
});
});
describe('#_subscribeZmqEvents', function() {
it('will call subscribe on zmq socket', function() {
var bitcoind = new BitcoinService(baseConfig);
var node = {
zmqSubSocket: {
subscribe: sinon.stub(),
on: sinon.stub()
}
};
bitcoind._subscribeZmqEvents(node);
node.zmqSubSocket.subscribe.callCount.should.equal(2);
node.zmqSubSocket.subscribe.args[0][0].should.equal('hashblock');
node.zmqSubSocket.subscribe.args[1][0].should.equal('rawtx');
});
it('will call relevant handler for rawtx topics', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._zmqTransactionHandler = sinon.stub();
var node = {
zmqSubSocket: new EventEmitter()
};
node.zmqSubSocket.subscribe = sinon.stub();
bitcoind._subscribeZmqEvents(node);
node.zmqSubSocket.on('message', function() {
bitcoind._zmqTransactionHandler.callCount.should.equal(1);
done();
});
var topic = new Buffer('rawtx', 'utf8');
var message = new Buffer('abcdef', 'hex');
node.zmqSubSocket.emit('message', topic, message);
});
it('will call relevant handler for hashblock topics', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._zmqBlockHandler = sinon.stub();
var node = {
zmqSubSocket: new EventEmitter()
};
node.zmqSubSocket.subscribe = sinon.stub();
bitcoind._subscribeZmqEvents(node);
node.zmqSubSocket.on('message', function() {
bitcoind._zmqBlockHandler.callCount.should.equal(1);
done();
});
var topic = new Buffer('hashblock', 'utf8');
var message = new Buffer('abcdef', 'hex');
node.zmqSubSocket.emit('message', topic, message);
});
it('will ignore unknown topic types', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._zmqBlockHandler = sinon.stub();
bitcoind._zmqTransactionHandler = sinon.stub();
var node = {
zmqSubSocket: new EventEmitter()
};
node.zmqSubSocket.subscribe = sinon.stub();
bitcoind._subscribeZmqEvents(node);
node.zmqSubSocket.on('message', function() {
bitcoind._zmqBlockHandler.callCount.should.equal(0);
bitcoind._zmqTransactionHandler.callCount.should.equal(0);
done();
});
var topic = new Buffer('unknown', 'utf8');
var message = new Buffer('abcdef', 'hex');
node.zmqSubSocket.emit('message', topic, message);
});
});
describe('#_initZmqSubSocket', function() {
it('will setup zmq socket', function() {
var socket = new EventEmitter();
socket.monitor = sinon.stub();
socket.connect = sinon.stub();
var socketFunc = function() {
return socket;
};
var BitcoinService = proxyquire('../../lib/services/bitcoind', {
zmq: {
socket: socketFunc
}
});
var bitcoind = new BitcoinService(baseConfig);
var node = {};
bitcoind._initZmqSubSocket(node, 'url');
node.zmqSubSocket.should.equal(socket);
socket.connect.callCount.should.equal(1);
socket.connect.args[0][0].should.equal('url');
socket.monitor.callCount.should.equal(1);
socket.monitor.args[0][0].should.equal(500);
socket.monitor.args[0][1].should.equal(0);
});
});
describe('#_checkReindex', function() {
var sandbox = sinon.sandbox.create();
before(function() {
sandbox.stub(log, 'info');
});
after(function() {
sandbox.restore();
});
it('give error from client getblockchaininfo', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var node = {
_reindex: true,
_reindexWait: 1,
client: {
getBlockchainInfo: sinon.stub().callsArgWith(0, {code: -1 , message: 'Test error'})
}
};
bitcoind._checkReindex(node, function(err) {
should.exist(err);
err.should.be.instanceof(errors.RPCError);
done();
});
});
it('will wait until sync is 100 percent', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var percent = 0.89;
var node = {
_reindex: true,
_reindexWait: 1,
client: {
getBlockchainInfo: function(callback) {
percent += 0.01;
callback(null, {
result: {
verificationprogress: percent
}
});
}
}
};
bitcoind._checkReindex(node, function() {
node._reindex.should.equal(false);
log.info.callCount.should.equal(11);
done();
});
});
it('will call callback if reindex is not enabled', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var node = {
_reindex: false
};
bitcoind._checkReindex(node, function() {
node._reindex.should.equal(false);
done();
});
});
});
describe('#_loadTipFromNode', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'warn');
});
afterEach(function() {
sandbox.restore();
});
it('will give rpc from client getbestblockhash', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBestBlockHash = sinon.stub().callsArgWith(0, {code: -1, message: 'Test error'});
var node = {
client: {
getBestBlockHash: getBestBlockHash
}
};
bitcoind._loadTipFromNode(node, function(err) {
err.should.be.instanceof(Error);
log.warn.callCount.should.equal(0);
done();
});
});
it('will give rpc from client getblock', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBestBlockHash = sinon.stub().callsArgWith(0, null, {
result: '00000000000000001bb82a7f5973618cfd3185ba1ded04dd852a653f92a27c45'
});
var getBlock = sinon.stub().callsArgWith(1, new Error('Test error'));
var node = {
client: {
getBestBlockHash: getBestBlockHash,
getBlock: getBlock
}
};
bitcoind._loadTipFromNode(node, function(err) {
getBlock.args[0][0].should.equal('00000000000000001bb82a7f5973618cfd3185ba1ded04dd852a653f92a27c45');
err.should.be.instanceof(Error);
log.warn.callCount.should.equal(0);
done();
});
});
it('will log when error is RPC_IN_WARMUP', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBestBlockHash = sinon.stub().callsArgWith(0, {code: -28, message: 'Verifying blocks...'});
var node = {
client: {
getBestBlockHash: getBestBlockHash
}
};
bitcoind._loadTipFromNode(node, function(err) {
err.should.be.instanceof(Error);
log.warn.callCount.should.equal(1);
done();
});
});
it('will set height and emit tip', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBestBlockHash = sinon.stub().callsArgWith(0, null, {
result: '00000000000000001bb82a7f5973618cfd3185ba1ded04dd852a653f92a27c45'
});
var getBlock = sinon.stub().callsArgWith(1, null, {
result: {
height: 100
}
});
var node = {
client: {
getBestBlockHash: getBestBlockHash,
getBlock: getBlock
}
};
bitcoind.on('tip', function(height) {
height.should.equal(100);
bitcoind.height.should.equal(100);
done();
});
bitcoind._loadTipFromNode(node, function(err) {
if (err) {
return done(err);
}
});
});
});
describe('#_stopSpawnedProcess', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'warn');
});
afterEach(function() {
sandbox.restore();
});
it('it will kill process and resume', function(done) {
var readFile = sandbox.stub();
readFile.onCall(0).callsArgWith(2, null, '4321');
var error = new Error('Test error');
error.code = 'ENOENT';
readFile.onCall(1).callsArgWith(2, error);
var TestBitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFile: readFile
}
});
var bitcoind = new TestBitcoinService(baseConfig);
bitcoind.spawnStopTime = 1;
bitcoind._process = {};
bitcoind._process.kill = sinon.stub();
bitcoind._stopSpawnedBitcoin(function(err) {
if (err) {
return done(err);
}
bitcoind._process.kill.callCount.should.equal(1);
log.warn.callCount.should.equal(1);
done();
});
});
it('it will attempt to kill process and resume', function(done) {
var readFile = sandbox.stub();
readFile.onCall(0).callsArgWith(2, null, '4321');
var error = new Error('Test error');
error.code = 'ENOENT';
readFile.onCall(1).callsArgWith(2, error);
var TestBitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFile: readFile
}
});
var bitcoind = new TestBitcoinService(baseConfig);
bitcoind.spawnStopTime = 1;
bitcoind._process = {};
var error2 = new Error('Test error');
error2.code = 'ESRCH';
bitcoind._process.kill = sinon.stub().throws(error2);
bitcoind._stopSpawnedBitcoin(function(err) {
if (err) {
return done(err);
}
bitcoind._process.kill.callCount.should.equal(1);
log.warn.callCount.should.equal(2);
done();
});
});
it('it will attempt to kill process with NaN', function(done) {
var readFile = sandbox.stub();
readFile.onCall(0).callsArgWith(2, null, ' ');
var TestBitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFile: readFile
}
});
var bitcoind = new TestBitcoinService(baseConfig);
bitcoind.spawnStopTime = 1;
bitcoind._process = {};
bitcoind._process.kill = sinon.stub();
bitcoind._stopSpawnedBitcoin(function(err) {
if (err) {
return done(err);
}
done();
});
});
it('it will attempt to kill process without pid', function(done) {
var readFile = sandbox.stub();
readFile.onCall(0).callsArgWith(2, null, '');
var TestBitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFile: readFile
}
});
var bitcoind = new TestBitcoinService(baseConfig);
bitcoind.spawnStopTime = 1;
bitcoind._process = {};
bitcoind._process.kill = sinon.stub();
bitcoind._stopSpawnedBitcoin(function(err) {
if (err) {
return done(err);
}
done();
});
});
});
describe('#_spawnChildProcess', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'info');
sandbox.stub(log, 'warn');
sandbox.stub(log, 'error');
});
afterEach(function() {
sandbox.restore();
});
it('will give error from spawn config', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._loadSpawnConfiguration = sinon.stub();
bitcoind._loadSpawnConfiguration = sinon.stub().throws(new Error('test'));
bitcoind._spawnChildProcess(function(err) {
err.should.be.instanceof(Error);
err.message.should.equal('test');
done();
});
});
it('will give error from stopSpawnedBitcoin', function() {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._loadSpawnConfiguration = sinon.stub();
bitcoind._stopSpawnedBitcoin = sinon.stub().callsArgWith(0, new Error('test'));
bitcoind._spawnChildProcess(function(err) {
err.should.be.instanceOf(Error);
err.message.should.equal('test');
});
});
it('will exit spawn if shutdown', function() {
var config = {
node: {
network: bitcore.Networks.testnet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
var process = new EventEmitter();
var spawn = sinon.stub().returns(process);
var TestBitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFileSync: readFileSync
},
child_process: {
spawn: spawn
}
});
var bitcoind = new TestBitcoinService(config);
bitcoind.spawn = {};
bitcoind._loadSpawnConfiguration = sinon.stub();
bitcoind._stopSpawnedBitcoin = sinon.stub().callsArgWith(0, null);
bitcoind.node.stopping = true;
bitcoind._spawnChildProcess(function(err) {
err.should.be.instanceOf(Error);
err.message.should.match(/Stopping while trying to spawn/);
});
});
it('will include network with spawn command and init zmq/rpc on node', function(done) {
var process = new EventEmitter();
var spawn = sinon.stub().returns(process);
var TestBitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFileSync: readFileSync
},
child_process: {
spawn: spawn
}
});
var bitcoind = new TestBitcoinService(baseConfig);
bitcoind._loadSpawnConfiguration = sinon.stub();
bitcoind.spawn = {};
bitcoind.spawn.exec = 'testexec';
bitcoind.spawn.configPath = 'testdir/bitcoin.conf';
bitcoind.spawn.datadir = 'testdir';
bitcoind.spawn.config = {};
bitcoind.spawn.config.rpcport = 20001;
bitcoind.spawn.config.rpcuser = 'bitcoin';
bitcoind.spawn.config.rpcpassword = 'password';
bitcoind.spawn.config.zmqpubrawtx = 'tcp://127.0.0.1:30001';
bitcoind._loadTipFromNode = sinon.stub().callsArgWith(1, null);
bitcoind._initZmqSubSocket = sinon.stub();
bitcoind._checkSyncedAndSubscribeZmqEvents = sinon.stub();
bitcoind._checkReindex = sinon.stub().callsArgWith(1, null);
bitcoind._spawnChildProcess(function(err, node) {
should.not.exist(err);
spawn.callCount.should.equal(1);
spawn.args[0][0].should.equal('testexec');
spawn.args[0][1].should.deep.equal([
'--conf=testdir/bitcoin.conf',
'--datadir=testdir',
'--testnet'
]);
spawn.args[0][2].should.deep.equal({
stdio: 'inherit'
});
bitcoind._loadTipFromNode.callCount.should.equal(1);
bitcoind._initZmqSubSocket.callCount.should.equal(1);
should.exist(bitcoind._initZmqSubSocket.args[0][0].client);
bitcoind._initZmqSubSocket.args[0][1].should.equal('tcp://127.0.0.1:30001');
bitcoind._checkSyncedAndSubscribeZmqEvents.callCount.should.equal(1);
should.exist(bitcoind._checkSyncedAndSubscribeZmqEvents.args[0][0].client);
should.exist(node);
should.exist(node.client);
done();
});
});
it('will respawn bitcoind spawned process', function(done) {
var process = new EventEmitter();
var spawn = sinon.stub().returns(process);
var TestBitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFileSync: readFileSync
},
child_process: {
spawn: spawn
}
});
var bitcoind = new TestBitcoinService(baseConfig);
bitcoind._loadSpawnConfiguration = sinon.stub();
bitcoind.spawn = {};
bitcoind.spawn.exec = 'bitcoind';
bitcoind.spawn.datadir = '/tmp/bitcoin';
bitcoind.spawn.configPath = '/tmp/bitcoin/bitcoin.conf';
bitcoind.spawn.config = {};
bitcoind.spawnRestartTime = 1;
bitcoind._loadTipFromNode = sinon.stub().callsArg(1);
bitcoind._initZmqSubSocket = sinon.stub();
bitcoind._checkReindex = sinon.stub().callsArg(1);
bitcoind._checkSyncedAndSubscribeZmqEvents = sinon.stub();
bitcoind._stopSpawnedBitcoin = sinon.stub().callsArg(0);
sinon.spy(bitcoind, '_spawnChildProcess');
bitcoind._spawnChildProcess(function(err) {
if (err) {
return done(err);
}
process.once('exit', function() {
setTimeout(function() {
bitcoind._spawnChildProcess.callCount.should.equal(2);
done();
}, 5);
});
process.emit('exit', 1);
});
});
it('will emit error during respawn', function(done) {
var process = new EventEmitter();
var spawn = sinon.stub().returns(process);
var TestBitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFileSync: readFileSync
},
child_process: {
spawn: spawn
}
});
var bitcoind = new TestBitcoinService(baseConfig);
bitcoind._loadSpawnConfiguration = sinon.stub();
bitcoind.spawn = {};
bitcoind.spawn.exec = 'bitcoind';
bitcoind.spawn.datadir = '/tmp/bitcoin';
bitcoind.spawn.configPath = '/tmp/bitcoin/bitcoin.conf';
bitcoind.spawn.config = {};
bitcoind.spawnRestartTime = 1;
bitcoind._loadTipFromNode = sinon.stub().callsArg(1);
bitcoind._initZmqSubSocket = sinon.stub();
bitcoind._checkReindex = sinon.stub().callsArg(1);
bitcoind._checkSyncedAndSubscribeZmqEvents = sinon.stub();
bitcoind._stopSpawnedBitcoin = sinon.stub().callsArg(0);
sinon.spy(bitcoind, '_spawnChildProcess');
bitcoind._spawnChildProcess(function(err) {
if (err) {
return done(err);
}
bitcoind._spawnChildProcess = sinon.stub().callsArgWith(0, new Error('test'));
bitcoind.on('error', function(err) {
err.should.be.instanceOf(Error);
err.message.should.equal('test');
done();
});
process.emit('exit', 1);
});
});
it('will NOT respawn bitcoind spawned process if shutting down', function(done) {
var process = new EventEmitter();
var spawn = sinon.stub().returns(process);
var TestBitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFileSync: readFileSync
},
child_process: {
spawn: spawn
}
});
var config = {
node: {
network: bitcore.Networks.testnet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
var bitcoind = new TestBitcoinService(config);
bitcoind._loadSpawnConfiguration = sinon.stub();
bitcoind.spawn = {};
bitcoind.spawn.exec = 'bitcoind';
bitcoind.spawn.datadir = '/tmp/bitcoin';
bitcoind.spawn.configPath = '/tmp/bitcoin/bitcoin.conf';
bitcoind.spawn.config = {};
bitcoind.spawnRestartTime = 1;
bitcoind._loadTipFromNode = sinon.stub().callsArg(1);
bitcoind._initZmqSubSocket = sinon.stub();
bitcoind._checkReindex = sinon.stub().callsArg(1);
bitcoind._checkSyncedAndSubscribeZmqEvents = sinon.stub();
bitcoind._stopSpawnedBitcoin = sinon.stub().callsArg(0);
sinon.spy(bitcoind, '_spawnChildProcess');
bitcoind._spawnChildProcess(function(err) {
if (err) {
return done(err);
}
bitcoind.node.stopping = true;
process.once('exit', function() {
setTimeout(function() {
bitcoind._spawnChildProcess.callCount.should.equal(1);
done();
}, 5);
});
process.emit('exit', 1);
});
});
it('will give error after 60 retries', function(done) {
var process = new EventEmitter();
var spawn = sinon.stub().returns(process);
var TestBitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFileSync: readFileSync
},
child_process: {
spawn: spawn
}
});
var bitcoind = new TestBitcoinService(baseConfig);
bitcoind.startRetryInterval = 1;
bitcoind._loadSpawnConfiguration = sinon.stub();
bitcoind.spawn = {};
bitcoind.spawn.exec = 'testexec';
bitcoind.spawn.configPath = 'testdir/bitcoin.conf';
bitcoind.spawn.datadir = 'testdir';
bitcoind.spawn.config = {};
bitcoind.spawn.config.rpcport = 20001;
bitcoind.spawn.config.rpcuser = 'bitcoin';
bitcoind.spawn.config.rpcpassword = 'password';
bitcoind.spawn.config.zmqpubrawtx = 'tcp://127.0.0.1:30001';
bitcoind._loadTipFromNode = sinon.stub().callsArgWith(1, new Error('test'));
bitcoind._spawnChildProcess(function(err) {
bitcoind._loadTipFromNode.callCount.should.equal(60);
err.should.be.instanceof(Error);
done();
});
});
it('will give error from check reindex', function(done) {
var process = new EventEmitter();
var spawn = sinon.stub().returns(process);
var TestBitcoinService = proxyquire('../../lib/services/bitcoind', {
fs: {
readFileSync: readFileSync
},
child_process: {
spawn: spawn
}
});
var bitcoind = new TestBitcoinService(baseConfig);
bitcoind._loadSpawnConfiguration = sinon.stub();
bitcoind.spawn = {};
bitcoind.spawn.exec = 'testexec';
bitcoind.spawn.configPath = 'testdir/bitcoin.conf';
bitcoind.spawn.datadir = 'testdir';
bitcoind.spawn.config = {};
bitcoind.spawn.config.rpcport = 20001;
bitcoind.spawn.config.rpcuser = 'bitcoin';
bitcoind.spawn.config.rpcpassword = 'password';
bitcoind.spawn.config.zmqpubrawtx = 'tcp://127.0.0.1:30001';
bitcoind._loadTipFromNode = sinon.stub().callsArgWith(1, null);
bitcoind._initZmqSubSocket = sinon.stub();
bitcoind._checkSyncedAndSubscribeZmqEvents = sinon.stub();
bitcoind._checkReindex = sinon.stub().callsArgWith(1, new Error('test'));
bitcoind._spawnChildProcess(function(err) {
err.should.be.instanceof(Error);
done();
});
});
});
describe('#_connectProcess', function() {
it('will give error if connecting while shutting down', function(done) {
var config = {
node: {
network: bitcore.Networks.testnet
},
spawn: {
datadir: 'testdir',
exec: 'testpath'
}
};
var bitcoind = new BitcoinService(config);
bitcoind.node.stopping = true;
bitcoind.startRetryInterval = 100;
bitcoind._loadTipFromNode = sinon.stub();
bitcoind._connectProcess({}, function(err) {
err.should.be.instanceof(Error);
err.message.should.match(/Stopping while trying to connect/);
bitcoind._loadTipFromNode.callCount.should.equal(0);
done();
});
});
it('will give error from loadTipFromNode after 60 retries', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._loadTipFromNode = sinon.stub().callsArgWith(1, new Error('test'));
bitcoind.startRetryInterval = 1;
var config = {};
bitcoind._connectProcess(config, function(err) {
err.should.be.instanceof(Error);
bitcoind._loadTipFromNode.callCount.should.equal(60);
done();
});
});
it('will init zmq/rpc on node', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._initZmqSubSocket = sinon.stub();
bitcoind._subscribeZmqEvents = sinon.stub();
bitcoind._loadTipFromNode = sinon.stub().callsArgWith(1, null);
var config = {};
bitcoind._connectProcess(config, function(err, node) {
should.not.exist(err);
bitcoind._loadTipFromNode.callCount.should.equal(1);
bitcoind._initZmqSubSocket.callCount.should.equal(1);
bitcoind._loadTipFromNode.callCount.should.equal(1);
should.exist(node);
should.exist(node.client);
done();
});
});
});
describe('#start', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'info');
});
afterEach(function() {
sandbox.restore();
});
it('will give error if "spawn" and "connect" are both not configured', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.options = {};
bitcoind.start(function(err) {
err.should.be.instanceof(Error);
err.message.should.match(/Bitcoin configuration options/);
});
done();
});
it('will give error from spawnChildProcess', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._spawnChildProcess = sinon.stub().callsArgWith(0, new Error('test'));
bitcoind.options = {
spawn: {}
};
bitcoind.start(function(err) {
err.should.be.instanceof(Error);
err.message.should.equal('test');
done();
});
});
it('will give error from connectProcess', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._connectProcess = sinon.stub().callsArgWith(1, new Error('test'));
bitcoind.options = {
connect: [
{}
]
};
bitcoind.start(function(err) {
bitcoind._connectProcess.callCount.should.equal(1);
err.should.be.instanceof(Error);
err.message.should.equal('test');
done();
});
});
it('will push node from spawnChildProcess', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var node = {};
bitcoind._initChain = sinon.stub().callsArg(0);
bitcoind._spawnChildProcess = sinon.stub().callsArgWith(0, null, node);
bitcoind.options = {
spawn: {}
};
bitcoind.start(function(err) {
should.not.exist(err);
bitcoind.nodes.length.should.equal(1);
done();
});
});
it('will push node from connectProcess', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._initChain = sinon.stub().callsArg(0);
var nodes = [{}];
bitcoind._connectProcess = sinon.stub().callsArgWith(1, null, nodes);
bitcoind.options = {
connect: [
{}
]
};
bitcoind.start(function(err) {
should.not.exist(err);
bitcoind._connectProcess.callCount.should.equal(1);
bitcoind.nodes.length.should.equal(1);
done();
});
});
});
describe('#isSynced', function() {
it('will give error from syncPercentage', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.syncPercentage = sinon.stub().callsArgWith(0, new Error('test'));
bitcoind.isSynced(function(err) {
should.exist(err);
err.message.should.equal('test');
done();
});
});
it('will give "true" if percentage is 100.00', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.syncPercentage = sinon.stub().callsArgWith(0, null, 100.00);
bitcoind.isSynced(function(err, synced) {
if (err) {
return done(err);
}
synced.should.equal(true);
done();
});
});
it('will give "true" if percentage is 99.98', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.syncPercentage = sinon.stub().callsArgWith(0, null, 99.98);
bitcoind.isSynced(function(err, synced) {
if (err) {
return done(err);
}
synced.should.equal(true);
done();
});
});
it('will give "false" if percentage is 99.49', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.syncPercentage = sinon.stub().callsArgWith(0, null, 99.49);
bitcoind.isSynced(function(err, synced) {
if (err) {
return done(err);
}
synced.should.equal(false);
done();
});
});
it('will give "false" if percentage is 1', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.syncPercentage = sinon.stub().callsArgWith(0, null, 1);
bitcoind.isSynced(function(err, synced) {
if (err) {
return done(err);
}
synced.should.equal(false);
done();
});
});
});
describe('#syncPercentage', function() {
it('will give rpc error', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlockchainInfo = sinon.stub().callsArgWith(0, {message: 'error', code: -1});
bitcoind.nodes.push({
client: {
getBlockchainInfo: getBlockchainInfo
}
});
bitcoind.syncPercentage(function(err) {
should.exist(err);
err.should.be.an.instanceof(errors.RPCError);
done();
});
});
it('will call client getInfo and give result', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlockchainInfo = sinon.stub().callsArgWith(0, null, {
result: {
verificationprogress: '0.983821387'
}
});
bitcoind.nodes.push({
client: {
getBlockchainInfo: getBlockchainInfo
}
});
bitcoind.syncPercentage(function(err, percentage) {
if (err) {
return done(err);
}
percentage.should.equal(98.3821387);
done();
});
});
});
describe('#_normalizeAddressArg', function() {
it('will turn single address into array', function() {
var bitcoind = new BitcoinService(baseConfig);
var args = bitcoind._normalizeAddressArg('address');
args.should.deep.equal(['address']);
});
it('will keep an array as an array', function() {
var bitcoind = new BitcoinService(baseConfig);
var args = bitcoind._normalizeAddressArg(['address', 'address']);
args.should.deep.equal(['address', 'address']);
});
});
describe('#getAddressBalance', function() {
it('will give rpc error', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressBalance: sinon.stub().callsArgWith(1, {code: -1, message: 'Test error'})
}
});
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
var options = {};
bitcoind.getAddressBalance(address, options, function(err) {
err.should.be.instanceof(Error);
done();
});
});
it('will give balance', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getAddressBalance = sinon.stub().callsArgWith(1, null, {
result: {
received: 100000,
balance: 10000
}
});
bitcoind.nodes.push({
client: {
getAddressBalance: getAddressBalance
}
});
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
var options = {};
bitcoind.getAddressBalance(address, options, function(err, data) {
if (err) {
return done(err);
}
data.balance.should.equal(10000);
data.received.should.equal(100000);
bitcoind.getAddressBalance(address, options, function(err, data2) {
if (err) {
return done(err);
}
data2.balance.should.equal(10000);
data2.received.should.equal(100000);
getAddressBalance.callCount.should.equal(1);
done();
});
});
});
});
describe('#getAddressUnspentOutputs', function() {
it('will give rpc error', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressUtxos: sinon.stub().callsArgWith(1, {code: -1, message: 'Test error'})
}
});
var options = {
queryMempool: false
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressUnspentOutputs(address, options, function(err) {
should.exist(err);
err.should.be.instanceof(errors.RPCError);
done();
});
});
it('will give results from client getaddressutxos', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var expectedUtxos = [
{
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
txid: '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0',
outputIndex: 1,
script: '76a914f399b4b8894f1153b96fce29f05e6e116eb4c21788ac',
satoshis: 7679241,
height: 207111
}
];
bitcoind.nodes.push({
client: {
getAddressUtxos: sinon.stub().callsArgWith(1, null, {
result: expectedUtxos
})
}
});
var options = {
queryMempool: false
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressUnspentOutputs(address, options, function(err, utxos) {
if (err) {
return done(err);
}
utxos.length.should.equal(1);
utxos.should.deep.equal(expectedUtxos);
done();
});
});
it('will use cache', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var expectedUtxos = [
{
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
txid: '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0',
outputIndex: 1,
script: '76a914f399b4b8894f1153b96fce29f05e6e116eb4c21788ac',
satoshis: 7679241,
height: 207111
}
];
var getAddressUtxos = sinon.stub().callsArgWith(1, null, {
result: expectedUtxos
});
bitcoind.nodes.push({
client: {
getAddressUtxos: getAddressUtxos
}
});
var options = {
queryMempool: false
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressUnspentOutputs(address, options, function(err, utxos) {
if (err) {
return done(err);
}
utxos.length.should.equal(1);
utxos.should.deep.equal(expectedUtxos);
getAddressUtxos.callCount.should.equal(1);
bitcoind.getAddressUnspentOutputs(address, options, function(err, utxos) {
if (err) {
return done(err);
}
utxos.length.should.equal(1);
utxos.should.deep.equal(expectedUtxos);
getAddressUtxos.callCount.should.equal(1);
done();
});
});
});
it('will update with mempool results', function(done) {
var deltas = [
{
txid: 'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce',
satoshis: -7679241,
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
index: 0,
timestamp: 1461342707725,
prevtxid: '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0',
prevout: 1
},
{
txid: 'f637384e9f81f18767ea50e00bce58fc9848b6588a1130529eebba22a410155f',
satoshis: 100000,
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
index: 0,
timestamp: 1461342833133
},
{
txid: 'f71bccef3a8f5609c7f016154922adbfe0194a96fb17a798c24077c18d0a9345',
satoshis: 400000,
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
index: 1,
timestamp: 1461342954813
}
];
var bitcoind = new BitcoinService(baseConfig);
var confirmedUtxos = [
{
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
txid: '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0',
outputIndex: 1,
script: '76a914f399b4b8894f1153b96fce29f05e6e116eb4c21788ac',
satoshis: 7679241,
height: 207111
}
];
var expectedUtxos = [
{
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
outputIndex: 1,
satoshis: 400000,
script: '76a914809dc14496f99b6deb722cf46d89d22f4beb8efd88ac',
timestamp: 1461342954813,
txid: 'f71bccef3a8f5609c7f016154922adbfe0194a96fb17a798c24077c18d0a9345'
},
{
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
outputIndex: 0,
satoshis: 100000,
script: '76a914809dc14496f99b6deb722cf46d89d22f4beb8efd88ac',
timestamp: 1461342833133,
txid: 'f637384e9f81f18767ea50e00bce58fc9848b6588a1130529eebba22a410155f'
}
];
bitcoind.nodes.push({
client: {
getAddressUtxos: sinon.stub().callsArgWith(1, null, {
result: confirmedUtxos
}),
getAddressMempool: sinon.stub().callsArgWith(1, null, {
result: deltas
})
}
});
var options = {
queryMempool: true
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressUnspentOutputs(address, options, function(err, utxos) {
if (err) {
return done(err);
}
utxos.length.should.equal(2);
utxos.should.deep.equal(expectedUtxos);
done();
});
});
it('will update with mempool results with multiple outputs', function(done) {
var deltas = [
{
txid: 'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce',
satoshis: -7679241,
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
index: 0,
timestamp: 1461342707725,
prevtxid: '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0',
prevout: 1
},
{
txid: 'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce',
satoshis: -7679241,
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
index: 1,
timestamp: 1461342707725,
prevtxid: '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0',
prevout: 2
}
];
var bitcoind = new BitcoinService(baseConfig);
var confirmedUtxos = [
{
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
txid: '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0',
outputIndex: 1,
script: '76a914f399b4b8894f1153b96fce29f05e6e116eb4c21788ac',
satoshis: 7679241,
height: 207111
},
{
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
txid: '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0',
outputIndex: 2,
script: '76a914f399b4b8894f1153b96fce29f05e6e116eb4c21788ac',
satoshis: 7679241,
height: 207111
}
];
bitcoind.nodes.push({
client: {
getAddressUtxos: sinon.stub().callsArgWith(1, null, {
result: confirmedUtxos
}),
getAddressMempool: sinon.stub().callsArgWith(1, null, {
result: deltas
})
}
});
var options = {
queryMempool: true
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressUnspentOutputs(address, options, function(err, utxos) {
if (err) {
return done(err);
}
utxos.length.should.equal(0);
done();
});
});
it('will update with mempool results spending zero value output (likely never to happen)', function(done) {
var deltas = [
{
txid: 'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce',
satoshis: 0,
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
index: 0,
timestamp: 1461342707725,
prevtxid: '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0',
prevout: 1
}
];
var bitcoind = new BitcoinService(baseConfig);
var confirmedUtxos = [
{
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
txid: '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0',
outputIndex: 1,
script: '76a914f399b4b8894f1153b96fce29f05e6e116eb4c21788ac',
satoshis: 0,
height: 207111
}
];
bitcoind.nodes.push({
client: {
getAddressUtxos: sinon.stub().callsArgWith(1, null, {
result: confirmedUtxos
}),
getAddressMempool: sinon.stub().callsArgWith(1, null, {
result: deltas
})
}
});
var options = {
queryMempool: true
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressUnspentOutputs(address, options, function(err, utxos) {
if (err) {
return done(err);
}
utxos.length.should.equal(0);
done();
});
});
it('will not filter results if mempool is not spending', function(done) {
var deltas = [
{
txid: 'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce',
satoshis: 10000,
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
index: 0,
timestamp: 1461342707725
}
];
var bitcoind = new BitcoinService(baseConfig);
var confirmedUtxos = [
{
address: '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo',
txid: '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0',
outputIndex: 1,
script: '76a914f399b4b8894f1153b96fce29f05e6e116eb4c21788ac',
satoshis: 0,
height: 207111
}
];
bitcoind.nodes.push({
client: {
getAddressUtxos: sinon.stub().callsArgWith(1, null, {
result: confirmedUtxos
}),
getAddressMempool: sinon.stub().callsArgWith(1, null, {
result: deltas
})
}
});
var options = {
queryMempool: true
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressUnspentOutputs(address, options, function(err, utxos) {
if (err) {
return done(err);
}
utxos.length.should.equal(2);
done();
});
});
it('it will handle error from getAddressMempool', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressMempool: sinon.stub().callsArgWith(1, {code: -1, message: 'test'})
}
});
var options = {
queryMempool: true
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressUnspentOutputs(address, options, function(err) {
err.should.be.instanceOf(Error);
done();
});
});
it('should set query mempool if undefined', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getAddressMempool = sinon.stub().callsArgWith(1, {code: -1, message: 'test'});
bitcoind.nodes.push({
client: {
getAddressMempool: getAddressMempool
}
});
var options = {};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressUnspentOutputs(address, options, function(err) {
getAddressMempool.callCount.should.equal(1);
done();
});
});
});
describe('#_getBalanceFromMempool', function() {
it('will sum satoshis', function() {
var bitcoind = new BitcoinService(baseConfig);
var deltas = [
{
satoshis: -1000,
},
{
satoshis: 2000,
},
{
satoshis: -10,
}
];
var sum = bitcoind._getBalanceFromMempool(deltas);
sum.should.equal(990);
});
});
describe('#_getTxidsFromMempool', function() {
it('will filter to txids', function() {
var bitcoind = new BitcoinService(baseConfig);
var deltas = [
{
txid: 'txid0',
},
{
txid: 'txid1',
},
{
txid: 'txid2',
}
];
var txids = bitcoind._getTxidsFromMempool(deltas);
txids.length.should.equal(3);
txids[0].should.equal('txid0');
txids[1].should.equal('txid1');
txids[2].should.equal('txid2');
});
it('will not include duplicates', function() {
var bitcoind = new BitcoinService(baseConfig);
var deltas = [
{
txid: 'txid0',
},
{
txid: 'txid0',
},
{
txid: 'txid1',
}
];
var txids = bitcoind._getTxidsFromMempool(deltas);
txids.length.should.equal(2);
txids[0].should.equal('txid0');
txids[1].should.equal('txid1');
});
});
describe('#_getHeightRangeQuery', function() {
it('will detect range query', function() {
var bitcoind = new BitcoinService(baseConfig);
var options = {
start: 20,
end: 0
};
var rangeQuery = bitcoind._getHeightRangeQuery(options);
rangeQuery.should.equal(true);
});
it('will get range properties', function() {
var bitcoind = new BitcoinService(baseConfig);
var options = {
start: 20,
end: 0
};
var clone = {};
bitcoind._getHeightRangeQuery(options, clone);
clone.end.should.equal(20);
clone.start.should.equal(0);
});
it('will throw error with invalid range', function() {
var bitcoind = new BitcoinService(baseConfig);
var options = {
start: 0,
end: 20
};
(function() {
bitcoind._getHeightRangeQuery(options);
}).should.throw('"end" is expected');
});
});
describe('#getAddressTxids', function() {
it('will give error from _getHeightRangeQuery', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._getHeightRangeQuery = sinon.stub().throws(new Error('test'));
bitcoind.getAddressTxids('address', {}, function(err) {
err.should.be.instanceOf(Error);
err.message.should.equal('test');
done();
});
});
it('will give rpc error from mempool query', function() {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressMempool: sinon.stub().callsArgWith(1, {code: -1, message: 'Test error'})
}
});
var options = {};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressTxids(address, options, function(err) {
should.exist(err);
err.should.be.instanceof(errors.RPCError);
});
});
it('will give rpc error from txids query', function() {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressTxids: sinon.stub().callsArgWith(1, {code: -1, message: 'Test error'})
}
});
var options = {
queryMempool: false
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressTxids(address, options, function(err) {
should.exist(err);
err.should.be.instanceof(errors.RPCError);
});
});
it('will get txid results', function(done) {
var expectedTxids = [
'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce',
'f637384e9f81f18767ea50e00bce58fc9848b6588a1130529eebba22a410155f',
'f3c1ba3ef86a0420d6102e40e2cfc8682632ab95d09d86a27f5d466b9fa9da47',
'56fafeb01961831b926558d040c246b97709fd700adcaa916541270583e8e579',
'bc992ad772eb02864db07ef248d31fb3c6826d25f1153ebf8c79df9b7f70fcf2',
'f71bccef3a8f5609c7f016154922adbfe0194a96fb17a798c24077c18d0a9345',
'f35e7e2a2334e845946f3eaca76890d9a68f4393ccc9fe37a0c2fb035f66d2e9',
'edc080f2084eed362aa488ccc873a24c378dc0979aa29b05767517b70569414a',
'ed11a08e3102f9610bda44c80c46781d97936a4290691d87244b1b345b39a693',
'ec94d845c603f292a93b7c829811ac624b76e52b351617ca5a758e9d61a11681'
];
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressTxids: sinon.stub().callsArgWith(1, null, {
result: expectedTxids.reverse()
})
}
});
var options = {
queryMempool: false
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressTxids(address, options, function(err, txids) {
if (err) {
return done(err);
}
txids.length.should.equal(expectedTxids.length);
txids.should.deep.equal(expectedTxids);
done();
});
});
it('will get txid results from cache', function(done) {
var expectedTxids = [
'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce'
];
var bitcoind = new BitcoinService(baseConfig);
var getAddressTxids = sinon.stub().callsArgWith(1, null, {
result: expectedTxids.reverse()
});
bitcoind.nodes.push({
client: {
getAddressTxids: getAddressTxids
}
});
var options = {
queryMempool: false
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressTxids(address, options, function(err, txids) {
if (err) {
return done(err);
}
getAddressTxids.callCount.should.equal(1);
txids.should.deep.equal(expectedTxids);
bitcoind.getAddressTxids(address, options, function(err, txids) {
if (err) {
return done(err);
}
getAddressTxids.callCount.should.equal(1);
txids.should.deep.equal(expectedTxids);
done();
});
});
});
it('will get txid results WITHOUT cache if rangeQuery and exclude mempool', function(done) {
var expectedTxids = [
'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce'
];
var bitcoind = new BitcoinService(baseConfig);
var getAddressMempool = sinon.stub();
var getAddressTxids = sinon.stub().callsArgWith(1, null, {
result: expectedTxids.reverse()
});
bitcoind.nodes.push({
client: {
getAddressTxids: getAddressTxids,
getAddressMempool: getAddressMempool
}
});
var options = {
queryMempool: true, // start and end will exclude mempool
start: 4,
end: 2
};
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressTxids(address, options, function(err, txids) {
if (err) {
return done(err);
}
getAddressTxids.callCount.should.equal(1);
getAddressMempool.callCount.should.equal(0);
txids.should.deep.equal(expectedTxids);
bitcoind.getAddressTxids(address, options, function(err, txids) {
if (err) {
return done(err);
}
getAddressTxids.callCount.should.equal(2);
getAddressMempool.callCount.should.equal(0);
txids.should.deep.equal(expectedTxids);
done();
});
});
});
it('will get txid results from cache and live mempool', function(done) {
var expectedTxids = [
'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce'
];
var bitcoind = new BitcoinService(baseConfig);
var getAddressTxids = sinon.stub().callsArgWith(1, null, {
result: expectedTxids.reverse()
});
var getAddressMempool = sinon.stub().callsArgWith(1, null, {
result: [
{
txid: 'bc992ad772eb02864db07ef248d31fb3c6826d25f1153ebf8c79df9b7f70fcf2'
},
{
txid: 'f71bccef3a8f5609c7f016154922adbfe0194a96fb17a798c24077c18d0a9345'
},
{
txid: 'f35e7e2a2334e845946f3eaca76890d9a68f4393ccc9fe37a0c2fb035f66d2e9'
}
]
});
bitcoind.nodes.push({
client: {
getAddressTxids: getAddressTxids,
getAddressMempool: getAddressMempool
}
});
var address = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
bitcoind.getAddressTxids(address, {queryMempool: false}, function(err, txids) {
if (err) {
return done(err);
}
getAddressTxids.callCount.should.equal(1);
txids.should.deep.equal(expectedTxids);
bitcoind.getAddressTxids(address, {queryMempool: true}, function(err, txids) {
if (err) {
return done(err);
}
getAddressTxids.callCount.should.equal(1);
txids.should.deep.equal([
'f35e7e2a2334e845946f3eaca76890d9a68f4393ccc9fe37a0c2fb035f66d2e9', // mempool
'f71bccef3a8f5609c7f016154922adbfe0194a96fb17a798c24077c18d0a9345', // mempool
'bc992ad772eb02864db07ef248d31fb3c6826d25f1153ebf8c79df9b7f70fcf2', // mempool
'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce' // confirmed
]);
done();
});
});
});
});
describe('#_getConfirmationDetail', function() {
var sandbox = sinon.sandbox.create();
beforeEach(function() {
sandbox.stub(log, 'warn');
});
afterEach(function() {
sandbox.restore();
});
it('should get 0 confirmation', function() {
var tx = new Transaction(txhex);
tx.height = -1;
var bitcoind = new BitcoinService(baseConfig);
bitcoind.height = 10;
var confirmations = bitcoind._getConfirmationsDetail(tx);
confirmations.should.equal(0);
});
it('should get 1 confirmation', function() {
var tx = new Transaction(txhex);
tx.height = 10;
var bitcoind = new BitcoinService(baseConfig);
bitcoind.height = 10;
var confirmations = bitcoind._getConfirmationsDetail(tx);
confirmations.should.equal(1);
});
it('should get 2 confirmation', function() {
var bitcoind = new BitcoinService(baseConfig);
var tx = new Transaction(txhex);
bitcoind.height = 11;
tx.height = 10;
var confirmations = bitcoind._getConfirmationsDetail(tx);
confirmations.should.equal(2);
});
it('should get 0 confirmation with overflow', function() {
var bitcoind = new BitcoinService(baseConfig);
var tx = new Transaction(txhex);
bitcoind.height = 3;
tx.height = 10;
var confirmations = bitcoind._getConfirmationsDetail(tx);
log.warn.callCount.should.equal(1);
confirmations.should.equal(0);
});
it('should get 1000 confirmation', function() {
var bitcoind = new BitcoinService(baseConfig);
var tx = new Transaction(txhex);
bitcoind.height = 1000;
tx.height = 1;
var confirmations = bitcoind._getConfirmationsDetail(tx);
confirmations.should.equal(1000);
});
});
describe('#_getAddressDetailsForInput', function() {
it('will return if missing an address', function() {
var bitcoind = new BitcoinService(baseConfig);
var result = {};
bitcoind._getAddressDetailsForInput({}, 0, result, []);
should.not.exist(result.addresses);
should.not.exist(result.satoshis);
});
it('will only add address if it matches', function() {
var bitcoind = new BitcoinService(baseConfig);
var result = {};
bitcoind._getAddressDetailsForInput({
address: 'address1'
}, 0, result, ['address2']);
should.not.exist(result.addresses);
should.not.exist(result.satoshis);
});
it('will instantiate if outputIndexes not defined', function() {
var bitcoind = new BitcoinService(baseConfig);
var result = {
addresses: {}
};
bitcoind._getAddressDetailsForInput({
address: 'address1'
}, 0, result, ['address1']);
should.exist(result.addresses);
result.addresses['address1'].inputIndexes.should.deep.equal([0]);
result.addresses['address1'].outputIndexes.should.deep.equal([]);
});
it('will push to inputIndexes', function() {
var bitcoind = new BitcoinService(baseConfig);
var result = {
addresses: {
'address1': {
inputIndexes: [1]
}
}
};
bitcoind._getAddressDetailsForInput({
address: 'address1'
}, 2, result, ['address1']);
should.exist(result.addresses);
result.addresses['address1'].inputIndexes.should.deep.equal([1, 2]);
});
});
describe('#_getAddressDetailsForOutput', function() {
it('will return if missing an address', function() {
var bitcoind = new BitcoinService(baseConfig);
var result = {};
bitcoind._getAddressDetailsForOutput({}, 0, result, []);
should.not.exist(result.addresses);
should.not.exist(result.satoshis);
});
it('will only add address if it matches', function() {
var bitcoind = new BitcoinService(baseConfig);
var result = {};
bitcoind._getAddressDetailsForOutput({
address: 'address1'
}, 0, result, ['address2']);
should.not.exist(result.addresses);
should.not.exist(result.satoshis);
});
it('will instantiate if outputIndexes not defined', function() {
var bitcoind = new BitcoinService(baseConfig);
var result = {
addresses: {}
};
bitcoind._getAddressDetailsForOutput({
address: 'address1'
}, 0, result, ['address1']);
should.exist(result.addresses);
result.addresses['address1'].inputIndexes.should.deep.equal([]);
result.addresses['address1'].outputIndexes.should.deep.equal([0]);
});
it('will push if outputIndexes defined', function() {
var bitcoind = new BitcoinService(baseConfig);
var result = {
addresses: {
'address1': {
outputIndexes: [0]
}
}
};
bitcoind._getAddressDetailsForOutput({
address: 'address1'
}, 1, result, ['address1']);
should.exist(result.addresses);
result.addresses['address1'].outputIndexes.should.deep.equal([0, 1]);
});
});
describe('#_getAddressDetailsForTransaction', function() {
it('will calculate details for the transaction', function(done) {
/* jshint sub:true */
var tx = {
inputs: [
{
satoshis: 1000000000,
address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'
}
],
outputs: [
{
satoshis: 100000000,
address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'
},
{
satoshis: 200000000,
address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'
},
{
satoshis: 50000000,
address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'
},
{
satoshis: 300000000,
address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'
},
{
satoshis: 349990000,
address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'
}
],
locktime: 0
};
var bitcoind = new BitcoinService(baseConfig);
var addresses = ['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'];
var details = bitcoind._getAddressDetailsForTransaction(tx, addresses);
should.exist(details.addresses['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW']);
details.addresses['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'].inputIndexes.should.deep.equal([0]);
details.addresses['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW'].outputIndexes.should.deep.equal([
0, 1, 2, 3, 4
]);
details.satoshis.should.equal(-10000);
done();
});
});
describe('#_getAddressDetailedTransaction', function() {
it('will get detailed transaction info', function(done) {
var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
var tx = {
height: 20,
};
var bitcoind = new BitcoinService(baseConfig);
bitcoind.getDetailedTransaction = sinon.stub().callsArgWith(1, null, tx);
bitcoind.height = 300;
var addresses = {};
bitcoind._getAddressDetailsForTransaction = sinon.stub().returns({
addresses: addresses,
satoshis: 1000,
});
bitcoind._getAddressDetailedTransaction(txid, {}, function(err, details) {
if (err) {
return done(err);
}
details.addresses.should.equal(addresses);
details.satoshis.should.equal(1000);
details.confirmations.should.equal(281);
details.tx.should.equal(tx);
done();
});
});
it('give error from getDetailedTransaction', function(done) {
var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
var bitcoind = new BitcoinService(baseConfig);
bitcoind.getDetailedTransaction = sinon.stub().callsArgWith(1, new Error('test'));
bitcoind._getAddressDetailedTransaction(txid, {}, function(err) {
err.should.be.instanceof(Error);
done();
});
});
});
describe('#_getAddressStrings', function() {
it('will get address strings from bitcore addresses', function() {
var addresses = [
bitcore.Address('1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i'),
bitcore.Address('3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou'),
];
var bitcoind = new BitcoinService(baseConfig);
var strings = bitcoind._getAddressStrings(addresses);
strings[0].should.equal('1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i');
strings[1].should.equal('3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou');
});
it('will get address strings from strings', function() {
var addresses = [
'1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i',
'3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou',
];
var bitcoind = new BitcoinService(baseConfig);
var strings = bitcoind._getAddressStrings(addresses);
strings[0].should.equal('1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i');
strings[1].should.equal('3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou');
});
it('will get address strings from mixture of types', function() {
var addresses = [
bitcore.Address('1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i'),
'3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou',
];
var bitcoind = new BitcoinService(baseConfig);
var strings = bitcoind._getAddressStrings(addresses);
strings[0].should.equal('1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i');
strings[1].should.equal('3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou');
});
it('will give error with unknown', function() {
var addresses = [
bitcore.Address('1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i'),
0,
];
var bitcoind = new BitcoinService(baseConfig);
(function() {
bitcoind._getAddressStrings(addresses);
}).should.throw(TypeError);
});
});
describe('#_paginateTxids', function() {
it('slice txids based on "from" and "to" (3 to 13)', function() {
var bitcoind = new BitcoinService(baseConfig);
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var paginated = bitcoind._paginateTxids(txids, 3, 13);
paginated.should.deep.equal([3, 4, 5, 6, 7, 8, 9, 10]);
});
it('slice txids based on "from" and "to" (0 to 3)', function() {
var bitcoind = new BitcoinService(baseConfig);
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var paginated = bitcoind._paginateTxids(txids, 0, 3);
paginated.should.deep.equal([0, 1, 2]);
});
it('slice txids based on "from" and "to" (0 to 1)', function() {
var bitcoind = new BitcoinService(baseConfig);
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var paginated = bitcoind._paginateTxids(txids, 0, 1);
paginated.should.deep.equal([0]);
});
it('will throw error if "from" is greater than "to"', function() {
var bitcoind = new BitcoinService(baseConfig);
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
(function() {
bitcoind._paginateTxids(txids, 1, 0);
}).should.throw('"from" (1) is expected to be less than "to"');
});
it('will handle string numbers', function() {
var bitcoind = new BitcoinService(baseConfig);
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var paginated = bitcoind._paginateTxids(txids, '1', '3');
paginated.should.deep.equal([1, 2]);
});
});
describe('#getAddressHistory', function() {
var address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX';
it('will give error with "from" and "to" range that exceeds max size', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.getAddressHistory(address, {from: 0, to: 51}, function(err) {
should.exist(err);
err.message.match(/^\"from/);
done();
});
});
it('will give error with "from" and "to" order is reversed', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, []);
bitcoind.getAddressHistory(address, {from: 51, to: 0}, function(err) {
should.exist(err);
err.message.match(/^\"from/);
done();
});
});
it('will give error from _getAddressDetailedTransaction', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, ['txid']);
bitcoind._getAddressDetailedTransaction = sinon.stub().callsArgWith(2, new Error('test'));
bitcoind.getAddressHistory(address, {}, function(err) {
should.exist(err);
err.message.should.equal('test');
done();
});
});
it('will give an error if length of addresses is too long', function(done) {
var addresses = [];
for (var i = 0; i < 101; i++) {
addresses.push(address);
}
var bitcoind = new BitcoinService(baseConfig);
bitcoind.maxAddressesQuery = 100;
bitcoind.getAddressHistory(addresses, {}, function(err) {
should.exist(err);
err.message.match(/Maximum/);
done();
});
});
it('give error from getAddressTxids', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, new Error('test'));
bitcoind.getAddressHistory('address', {}, function(err) {
should.exist(err);
err.should.be.instanceof(Error);
err.message.should.equal('test');
done();
});
});
it('will paginate', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._getAddressDetailedTransaction = function(txid, options, callback) {
callback(null, txid);
};
var txids = ['one', 'two', 'three', 'four'];
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, txids);
bitcoind.getAddressHistory('address', {from: 1, to: 3}, function(err, data) {
if (err) {
return done(err);
}
data.items.length.should.equal(2);
data.items.should.deep.equal(['two', 'three']);
done();
});
});
});
describe('#getAddressSummary', function() {
var txid1 = '70d9d441d7409aace8e0ffe24ff0190407b2fcb405799a266e0327017288d1f8';
var txid2 = '35fafaf572341798b2ce2858755afa7c8800bb6b1e885d3e030b81255b5e172d';
var txid3 = '57b7842afc97a2b46575b490839df46e9273524c6ea59ba62e1e86477cf25247';
var memtxid1 = 'b1bfa8dbbde790cb46b9763ef3407c1a21c8264b67bfe224f462ec0e1f569e92';
var memtxid2 = 'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce';
it('will handle error from getAddressTxids', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressMempool: sinon.stub().callsArgWith(1, null, {
result: [
{
txid: '70d9d441d7409aace8e0ffe24ff0190407b2fcb405799a266e0327017288d1f8',
}
]
})
}
});
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, new Error('test'));
bitcoind.getAddressBalance = sinon.stub().callsArgWith(2, null, {});
var address = '';
var options = {};
bitcoind.getAddressSummary(address, options, function(err) {
should.exist(err);
err.should.be.instanceof(Error);
err.message.should.equal('test');
done();
});
});
it('will handle error from getAddressBalance', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressMempool: sinon.stub().callsArgWith(1, null, {
result: [
{
txid: '70d9d441d7409aace8e0ffe24ff0190407b2fcb405799a266e0327017288d1f8',
}
]
})
}
});
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, {});
bitcoind.getAddressBalance = sinon.stub().callsArgWith(2, new Error('test'), {});
var address = '';
var options = {};
bitcoind.getAddressSummary(address, options, function(err) {
should.exist(err);
err.should.be.instanceof(Error);
err.message.should.equal('test');
done();
});
});
it('will handle error from client getAddressMempool', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressMempool: sinon.stub().callsArgWith(1, {code: -1, message: 'Test error'})
}
});
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, {});
bitcoind.getAddressBalance = sinon.stub().callsArgWith(2, null, {});
var address = '';
var options = {};
bitcoind.getAddressSummary(address, options, function(err) {
should.exist(err);
err.should.be.instanceof(Error);
err.message.should.equal('Test error');
done();
});
});
it('should set all properties', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressMempool: sinon.stub().callsArgWith(1, null, {
result: [
{
txid: memtxid1,
satoshis: -1000000
},
{
txid: memtxid2,
satoshis: 99999
}
]
})
}
});
sinon.spy(bitcoind, '_paginateTxids');
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, [txid1, txid2, txid3]);
bitcoind.getAddressBalance = sinon.stub().callsArgWith(2, null, {
received: 30 * 1e8,
balance: 20 * 1e8
});
var address = '3NbU8XzUgKyuCgYgZEKsBtUvkTm2r7Xgwj';
var options = {};
bitcoind.getAddressSummary(address, options, function(err, summary) {
bitcoind._paginateTxids.callCount.should.equal(1);
bitcoind._paginateTxids.args[0][1].should.equal(0);
bitcoind._paginateTxids.args[0][2].should.equal(1000);
summary.appearances.should.equal(3);
summary.totalReceived.should.equal(3000000000);
summary.totalSpent.should.equal(1000000000);
summary.balance.should.equal(2000000000);
summary.unconfirmedAppearances.should.equal(2);
summary.unconfirmedBalance.should.equal(-900001);
summary.txids.should.deep.equal([
'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce',
'b1bfa8dbbde790cb46b9763ef3407c1a21c8264b67bfe224f462ec0e1f569e92',
'70d9d441d7409aace8e0ffe24ff0190407b2fcb405799a266e0327017288d1f8',
'35fafaf572341798b2ce2858755afa7c8800bb6b1e885d3e030b81255b5e172d',
'57b7842afc97a2b46575b490839df46e9273524c6ea59ba62e1e86477cf25247'
]);
done();
});
});
it('will give error with "from" and "to" range that exceeds max size', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressMempool: sinon.stub().callsArgWith(1, null, {
result: [
{
txid: memtxid1,
satoshis: -1000000
},
{
txid: memtxid2,
satoshis: 99999
}
]
})
}
});
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, [txid1, txid2, txid3]);
bitcoind.getAddressBalance = sinon.stub().callsArgWith(2, null, {
received: 30 * 1e8,
balance: 20 * 1e8
});
var address = '3NbU8XzUgKyuCgYgZEKsBtUvkTm2r7Xgwj';
var options = {
from: 0,
to: 1001
};
bitcoind.getAddressSummary(address, options, function(err) {
should.exist(err);
err.message.match(/^\"from/);
done();
});
});
it('will get from cache with noTxList', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getAddressMempool: sinon.stub().callsArgWith(1, null, {
result: [
{
txid: memtxid1,
satoshis: -1000000
},
{
txid: memtxid2,
satoshis: 99999
}
]
})
}
});
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, [txid1, txid2, txid3]);
bitcoind.getAddressBalance = sinon.stub().callsArgWith(2, null, {
received: 30 * 1e8,
balance: 20 * 1e8
});
var address = '3NbU8XzUgKyuCgYgZEKsBtUvkTm2r7Xgwj';
var options = {
noTxList: true
};
function checkSummary(summary) {
summary.appearances.should.equal(3);
summary.totalReceived.should.equal(3000000000);
summary.totalSpent.should.equal(1000000000);
summary.balance.should.equal(2000000000);
summary.unconfirmedAppearances.should.equal(2);
summary.unconfirmedBalance.should.equal(-900001);
should.not.exist(summary.txids);
}
bitcoind.getAddressSummary(address, options, function(err, summary) {
checkSummary(summary);
bitcoind.getAddressTxids.callCount.should.equal(1);
bitcoind.getAddressBalance.callCount.should.equal(1);
bitcoind.getAddressSummary(address, options, function(err, summary) {
checkSummary(summary);
bitcoind.getAddressTxids.callCount.should.equal(1);
bitcoind.getAddressBalance.callCount.should.equal(1);
done();
});
});
});
it('will skip querying the mempool with queryMempool set to false', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getAddressMempool = sinon.stub();
bitcoind.nodes.push({
client: {
getAddressMempool: getAddressMempool
}
});
sinon.spy(bitcoind, '_paginateTxids');
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, [txid1, txid2, txid3]);
bitcoind.getAddressBalance = sinon.stub().callsArgWith(2, null, {
received: 30 * 1e8,
balance: 20 * 1e8
});
var address = '3NbU8XzUgKyuCgYgZEKsBtUvkTm2r7Xgwj';
var options = {
queryMempool: false
};
bitcoind.getAddressSummary(address, options, function() {
getAddressMempool.callCount.should.equal(0);
done();
});
});
it('will give error from _paginateTxids', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getAddressMempool = sinon.stub();
bitcoind.nodes.push({
client: {
getAddressMempool: getAddressMempool
}
});
sinon.spy(bitcoind, '_paginateTxids');
bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, [txid1, txid2, txid3]);
bitcoind.getAddressBalance = sinon.stub().callsArgWith(2, null, {
received: 30 * 1e8,
balance: 20 * 1e8
});
bitcoind._paginateTxids = sinon.stub().throws(new Error('test'));
var address = '3NbU8XzUgKyuCgYgZEKsBtUvkTm2r7Xgwj';
var options = {
queryMempool: false
};
bitcoind.getAddressSummary(address, options, function(err) {
err.should.be.instanceOf(Error);
err.message.should.equal('test');
done();
});
});
});
describe('#getRawBlock', function() {
var blockhash = '00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b';
var blockhex = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000';
it('will give rcp error from client getblockhash', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getBlockHash: sinon.stub().callsArgWith(1, {code: -1, message: 'Test error'})
}
});
bitcoind.getRawBlock(10, function(err) {
should.exist(err);
err.should.be.instanceof(errors.RPCError);
done();
});
});
it('will give rcp error from client getblock', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getBlock: sinon.stub().callsArgWith(2, {code: -1, message: 'Test error'})
}
});
bitcoind.getRawBlock(blockhash, function(err) {
should.exist(err);
err.should.be.instanceof(errors.RPCError);
done();
});
});
it('will try all nodes for getblock', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlockWithError = sinon.stub().callsArgWith(2, {code: -1, message: 'Test error'});
bitcoind.tryAllInterval = 1;
bitcoind.nodes.push({
client: {
getBlock: getBlockWithError
}
});
bitcoind.nodes.push({
client: {
getBlock: getBlockWithError
}
});
bitcoind.nodes.push({
client: {
getBlock: sinon.stub().callsArgWith(2, null, {
result: blockhex
})
}
});
bitcoind.getRawBlock(blockhash, function(err, buffer) {
if (err) {
return done(err);
}
buffer.should.be.instanceof(Buffer);
getBlockWithError.callCount.should.equal(2);
done();
});
});
it('will get block from cache', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlock = sinon.stub().callsArgWith(2, null, {
result: blockhex
});
bitcoind.nodes.push({
client: {
getBlock: getBlock
}
});
bitcoind.getRawBlock(blockhash, function(err, buffer) {
if (err) {
return done(err);
}
buffer.should.be.instanceof(Buffer);
getBlock.callCount.should.equal(1);
bitcoind.getRawBlock(blockhash, function(err, buffer) {
if (err) {
return done(err);
}
buffer.should.be.instanceof(Buffer);
getBlock.callCount.should.equal(1);
done();
});
});
});
it('will get block by height', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlock = sinon.stub().callsArgWith(2, null, {
result: blockhex
});
var getBlockHash = sinon.stub().callsArgWith(1, null, {
result: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
});
bitcoind.nodes.push({
client: {
getBlock: getBlock,
getBlockHash: getBlockHash
}
});
bitcoind.getRawBlock(0, function(err, buffer) {
if (err) {
return done(err);
}
buffer.should.be.instanceof(Buffer);
getBlock.callCount.should.equal(1);
getBlockHash.callCount.should.equal(1);
done();
});
});
});
describe('#getBlock', function() {
var blockhex = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000';
it('will give an rpc error from client getblock', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlock = sinon.stub().callsArgWith(2, {code: -1, message: 'Test error'});
var getBlockHash = sinon.stub().callsArgWith(1, null, {});
bitcoind.nodes.push({
client: {
getBlock: getBlock,
getBlockHash: getBlockHash
}
});
bitcoind.getBlock(0, function(err) {
err.should.be.instanceof(Error);
done();
});
});
it('will give an rpc error from client getblockhash', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlockHash = sinon.stub().callsArgWith(1, {code: -1, message: 'Test error'});
bitcoind.nodes.push({
client: {
getBlockHash: getBlockHash
}
});
bitcoind.getBlock(0, function(err) {
err.should.be.instanceof(Error);
done();
});
});
it('will getblock as bitcore object from height', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlock = sinon.stub().callsArgWith(2, null, {
result: blockhex
});
var getBlockHash = sinon.stub().callsArgWith(1, null, {
result: '00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b'
});
bitcoind.nodes.push({
client: {
getBlock: getBlock,
getBlockHash: getBlockHash
}
});
bitcoind.getBlock(0, function(err, block) {
should.not.exist(err);
getBlock.args[0][0].should.equal('00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b');
getBlock.args[0][1].should.equal(false);
block.should.be.instanceof(bitcore.Block);
done();
});
});
it('will getblock as bitcore object', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlock = sinon.stub().callsArgWith(2, null, {
result: blockhex
});
var getBlockHash = sinon.stub();
bitcoind.nodes.push({
client: {
getBlock: getBlock,
getBlockHash: getBlockHash
}
});
bitcoind.getBlock('00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b', function(err, block) {
should.not.exist(err);
getBlockHash.callCount.should.equal(0);
getBlock.callCount.should.equal(1);
getBlock.args[0][0].should.equal('00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b');
getBlock.args[0][1].should.equal(false);
block.should.be.instanceof(bitcore.Block);
done();
});
});
it('will get block from cache', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlock = sinon.stub().callsArgWith(2, null, {
result: blockhex
});
var getBlockHash = sinon.stub();
bitcoind.nodes.push({
client: {
getBlock: getBlock,
getBlockHash: getBlockHash
}
});
var hash = '00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b';
bitcoind.getBlock(hash, function(err, block) {
should.not.exist(err);
getBlockHash.callCount.should.equal(0);
getBlock.callCount.should.equal(1);
block.should.be.instanceof(bitcore.Block);
bitcoind.getBlock(hash, function(err, block) {
should.not.exist(err);
getBlockHash.callCount.should.equal(0);
getBlock.callCount.should.equal(1);
block.should.be.instanceof(bitcore.Block);
done();
});
});
});
it('will get block from cache with height (but not height)', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlock = sinon.stub().callsArgWith(2, null, {
result: blockhex
});
var getBlockHash = sinon.stub().callsArgWith(1, null, {
result: '00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b'
});
bitcoind.nodes.push({
client: {
getBlock: getBlock,
getBlockHash: getBlockHash
}
});
bitcoind.getBlock(0, function(err, block) {
should.not.exist(err);
getBlockHash.callCount.should.equal(1);
getBlock.callCount.should.equal(1);
block.should.be.instanceof(bitcore.Block);
bitcoind.getBlock(0, function(err, block) {
should.not.exist(err);
getBlockHash.callCount.should.equal(2);
getBlock.callCount.should.equal(1);
block.should.be.instanceof(bitcore.Block);
done();
});
});
});
});
describe('#getBlockHashesByTimestamp', function() {
it('should give an rpc error', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlockHashes = sinon.stub().callsArgWith(2, {message: 'error', code: -1});
bitcoind.nodes.push({
client: {
getBlockHashes: getBlockHashes
}
});
bitcoind.getBlockHashesByTimestamp(1441911000, 1441914000, function(err, hashes) {
should.exist(err);
err.message.should.equal('error');
done();
});
});
it('should get the correct block hashes', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var block1 = '00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b';
var block2 = '000000000383752a55a0b2891ce018fd0fdc0b6352502772b034ec282b4a1bf6';
var getBlockHashes = sinon.stub().callsArgWith(2, null, {
result: [block2, block1]
});
bitcoind.nodes.push({
client: {
getBlockHashes: getBlockHashes
}
});
bitcoind.getBlockHashesByTimestamp(1441914000, 1441911000, function(err, hashes) {
should.not.exist(err);
hashes.should.deep.equal([block2, block1]);
done();
});
});
});
describe('#getBlockHeader', function() {
var blockhash = '00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b';
it('will give error from getBlockHash', function() {
var bitcoind = new BitcoinService(baseConfig);
var getBlockHash = sinon.stub().callsArgWith(1, {code: -1, message: 'Test error'});
bitcoind.nodes.push({
client: {
getBlockHash: getBlockHash
}
});
bitcoind.getBlockHeader(10, function(err) {
err.should.be.instanceof(Error);
});
});
it('it will give rpc error from client getblockheader', function() {
var bitcoind = new BitcoinService(baseConfig);
var getBlockHeader = sinon.stub().callsArgWith(1, {code: -1, message: 'Test error'});
bitcoind.nodes.push({
client: {
getBlockHeader: getBlockHeader
}
});
bitcoind.getBlockHeader(blockhash, function(err) {
err.should.be.instanceof(Error);
});
});
it('it will give rpc error from client getblockhash', function() {
var bitcoind = new BitcoinService(baseConfig);
var getBlockHeader = sinon.stub();
var getBlockHash = sinon.stub().callsArgWith(1, {code: -1, message: 'Test error'});
bitcoind.nodes.push({
client: {
getBlockHeader: getBlockHeader,
getBlockHash: getBlockHash
}
});
bitcoind.getBlockHeader(0, function(err) {
err.should.be.instanceof(Error);
});
});
it('will give result from client getblockheader (from height)', function() {
var bitcoind = new BitcoinService(baseConfig);
var result = {
hash: '0000000000000a817cd3a74aec2f2246b59eb2cbb1ad730213e6c4a1d68ec2f6',
version: 536870912,
confirmations: 5,
height: 828781,
chainWork: '00000000000000000000000000000000000000000000000ad467352c93bc6a3b',
prevHash: '0000000000000504235b2aff578a48470dbf6b94dafa9b3703bbf0ed554c9dd9',
nextHash: '00000000000000eedd967ec155f237f033686f0924d574b946caf1b0e89551b8',
merkleRoot: '124e0f3fb5aa268f102b0447002dd9700988fc570efcb3e0b5b396ac7db437a9',
time: 1462979126,
medianTime: 1462976771,
nonce: 2981820714,
bits: '1a13ca10',
difficulty: 847779.0710240941
};
var getBlockHeader = sinon.stub().callsArgWith(1, null, {
result: {
hash: '0000000000000a817cd3a74aec2f2246b59eb2cbb1ad730213e6c4a1d68ec2f6',
version: 536870912,
confirmations: 5,
height: 828781,
chainwork: '00000000000000000000000000000000000000000000000ad467352c93bc6a3b',
previousblockhash: '0000000000000504235b2aff578a48470dbf6b94dafa9b3703bbf0ed554c9dd9',
nextblockhash: '00000000000000eedd967ec155f237f033686f0924d574b946caf1b0e89551b8',
merkleroot: '124e0f3fb5aa268f102b0447002dd9700988fc570efcb3e0b5b396ac7db437a9',
time: 1462979126,
mediantime: 1462976771,
nonce: 2981820714,
bits: '1a13ca10',
difficulty: 847779.0710240941
}
});
var getBlockHash = sinon.stub().callsArgWith(1, null, {
result: blockhash
});
bitcoind.nodes.push({
client: {
getBlockHeader: getBlockHeader,
getBlockHash: getBlockHash
}
});
bitcoind.getBlockHeader(0, function(err, blockHeader) {
should.not.exist(err);
getBlockHeader.args[0][0].should.equal(blockhash);
blockHeader.should.deep.equal(result);
});
});
it('will give result from client getblockheader (from hash)', function() {
var bitcoind = new BitcoinService(baseConfig);
var result = {
hash: '0000000000000a817cd3a74aec2f2246b59eb2cbb1ad730213e6c4a1d68ec2f6',
version: 536870912,
confirmations: 5,
height: 828781,
chainWork: '00000000000000000000000000000000000000000000000ad467352c93bc6a3b',
prevHash: '0000000000000504235b2aff578a48470dbf6b94dafa9b3703bbf0ed554c9dd9',
nextHash: '00000000000000eedd967ec155f237f033686f0924d574b946caf1b0e89551b8',
merkleRoot: '124e0f3fb5aa268f102b0447002dd9700988fc570efcb3e0b5b396ac7db437a9',
time: 1462979126,
medianTime: 1462976771,
nonce: 2981820714,
bits: '1a13ca10',
difficulty: 847779.0710240941
};
var getBlockHeader = sinon.stub().callsArgWith(1, null, {
result: {
hash: '0000000000000a817cd3a74aec2f2246b59eb2cbb1ad730213e6c4a1d68ec2f6',
version: 536870912,
confirmations: 5,
height: 828781,
chainwork: '00000000000000000000000000000000000000000000000ad467352c93bc6a3b',
previousblockhash: '0000000000000504235b2aff578a48470dbf6b94dafa9b3703bbf0ed554c9dd9',
nextblockhash: '00000000000000eedd967ec155f237f033686f0924d574b946caf1b0e89551b8',
merkleroot: '124e0f3fb5aa268f102b0447002dd9700988fc570efcb3e0b5b396ac7db437a9',
time: 1462979126,
mediantime: 1462976771,
nonce: 2981820714,
bits: '1a13ca10',
difficulty: 847779.0710240941
}
});
var getBlockHash = sinon.stub();
bitcoind.nodes.push({
client: {
getBlockHeader: getBlockHeader,
getBlockHash: getBlockHash
}
});
bitcoind.getBlockHeader(blockhash, function(err, blockHeader) {
should.not.exist(err);
getBlockHash.callCount.should.equal(0);
blockHeader.should.deep.equal(result);
});
});
});
describe('#_maybeGetBlockHash', function() {
it('will not get block hash with an address', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlockHash = sinon.stub();
bitcoind.nodes.push({
client: {
getBlockHash: getBlockHash
}
});
bitcoind._maybeGetBlockHash('2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br', function(err, hash) {
if (err) {
return done(err);
}
getBlockHash.callCount.should.equal(0);
hash.should.equal('2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br');
done();
});
});
it('will not get block hash with non zero-nine numeric string', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlockHash = sinon.stub();
bitcoind.nodes.push({
client: {
getBlockHash: getBlockHash
}
});
bitcoind._maybeGetBlockHash('109a', function(err, hash) {
if (err) {
return done(err);
}
getBlockHash.callCount.should.equal(0);
hash.should.equal('109a');
done();
});
});
it('will get the block hash if argument is a number', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlockHash = sinon.stub().callsArgWith(1, null, {
result: 'blockhash'
});
bitcoind.nodes.push({
client: {
getBlockHash: getBlockHash
}
});
bitcoind._maybeGetBlockHash(10, function(err, hash) {
if (err) {
return done(err);
}
hash.should.equal('blockhash');
getBlockHash.callCount.should.equal(1);
done();
});
});
it('will get the block hash if argument is a number (as string)', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlockHash = sinon.stub().callsArgWith(1, null, {
result: 'blockhash'
});
bitcoind.nodes.push({
client: {
getBlockHash: getBlockHash
}
});
bitcoind._maybeGetBlockHash('10', function(err, hash) {
if (err) {
return done(err);
}
hash.should.equal('blockhash');
getBlockHash.callCount.should.equal(1);
done();
});
});
it('will try multiple nodes if one fails', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlockHash = sinon.stub().callsArgWith(1, null, {
result: 'blockhash'
});
getBlockHash.onCall(0).callsArgWith(1, {code: -1, message: 'test'});
bitcoind.tryAllInterval = 1;
bitcoind.nodes.push({
client: {
getBlockHash: getBlockHash
}
});
bitcoind.nodes.push({
client: {
getBlockHash: getBlockHash
}
});
bitcoind._maybeGetBlockHash(10, function(err, hash) {
if (err) {
return done(err);
}
hash.should.equal('blockhash');
getBlockHash.callCount.should.equal(2);
done();
});
});
it('will give error from getBlockHash', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlockHash = sinon.stub().callsArgWith(1, {code: -1, message: 'test'});
bitcoind.tryAllInterval = 1;
bitcoind.nodes.push({
client: {
getBlockHash: getBlockHash
}
});
bitcoind.nodes.push({
client: {
getBlockHash: getBlockHash
}
});
bitcoind._maybeGetBlockHash(10, function(err, hash) {
getBlockHash.callCount.should.equal(2);
err.should.be.instanceOf(Error);
err.message.should.equal('test');
err.code.should.equal(-1);
done();
});
});
});
describe('#getBlockOverview', function() {
var blockhash = '00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b';
it('will handle error from maybeGetBlockHash', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind._maybeGetBlockHash = sinon.stub().callsArgWith(1, new Error('test'));
bitcoind.getBlockOverview(blockhash, function(err) {
err.should.be.instanceOf(Error);
done();
});
});
it('will give error from client.getBlock', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBlock = sinon.stub().callsArgWith(2, {code: -1, message: 'test'});
bitcoind.nodes.push({
client: {
getBlock: getBlock
}
});
bitcoind.getBlockOverview(blockhash, function(err) {
err.should.be.instanceOf(Error);
err.message.should.equal('test');
done();
});
});
it('will give expected result', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var blockResult = {
hash: blockhash,
version: 536870912,
confirmations: 5,
height: 828781,
chainwork: '00000000000000000000000000000000000000000000000ad467352c93bc6a3b',
previousblockhash: '0000000000000504235b2aff578a48470dbf6b94dafa9b3703bbf0ed554c9dd9',
nextblockhash: '00000000000000eedd967ec155f237f033686f0924d574b946caf1b0e89551b8',
merkleroot: '124e0f3fb5aa268f102b0447002dd9700988fc570efcb3e0b5b396ac7db437a9',
time: 1462979126,
mediantime: 1462976771,
nonce: 2981820714,
bits: '1a13ca10',
difficulty: 847779.0710240941
};
var getBlock = sinon.stub().callsArgWith(2, null, {
result: blockResult
});
bitcoind.nodes.push({
client: {
getBlock: getBlock
}
});
function checkBlock(blockOverview) {
blockOverview.hash.should.equal('00000000050a6d07f583beba2d803296eb1e9d4980c4a20f206c584e89a4f02b');
blockOverview.version.should.equal(536870912);
blockOverview.confirmations.should.equal(5);
blockOverview.height.should.equal(828781);
blockOverview.chainWork.should.equal('00000000000000000000000000000000000000000000000ad467352c93bc6a3b');
blockOverview.prevHash.should.equal('0000000000000504235b2aff578a48470dbf6b94dafa9b3703bbf0ed554c9dd9');
blockOverview.nextHash.should.equal('00000000000000eedd967ec155f237f033686f0924d574b946caf1b0e89551b8');
blockOverview.merkleRoot.should.equal('124e0f3fb5aa268f102b0447002dd9700988fc570efcb3e0b5b396ac7db437a9');
blockOverview.time.should.equal(1462979126);
blockOverview.medianTime.should.equal(1462976771);
blockOverview.nonce.should.equal(2981820714);
blockOverview.bits.should.equal('1a13ca10');
blockOverview.difficulty.should.equal(847779.0710240941);
}
bitcoind.getBlockOverview(blockhash, function(err, blockOverview) {
if (err) {
return done(err);
}
checkBlock(blockOverview);
bitcoind.getBlockOverview(blockhash, function(err, blockOverview) {
checkBlock(blockOverview);
getBlock.callCount.should.equal(1);
done();
});
});
});
});
describe('#estimateFee', function() {
it('will give rpc error', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var estimateFee = sinon.stub().callsArgWith(1, {message: 'error', code: -1});
bitcoind.nodes.push({
client: {
estimateFee: estimateFee
}
});
bitcoind.estimateFee(1, function(err) {
should.exist(err);
err.should.be.an.instanceof(errors.RPCError);
done();
});
});
it('will call client estimateFee and give result', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var estimateFee = sinon.stub().callsArgWith(1, null, {
result: -1
});
bitcoind.nodes.push({
client: {
estimateFee: estimateFee
}
});
bitcoind.estimateFee(1, function(err, feesPerKb) {
if (err) {
return done(err);
}
feesPerKb.should.equal(-1);
done();
});
});
});
describe('#sendTransaction', function(done) {
var tx = bitcore.Transaction(txhex);
it('will give rpc error', function() {
var bitcoind = new BitcoinService(baseConfig);
var sendRawTransaction = sinon.stub().callsArgWith(2, {message: 'error', code: -1});
bitcoind.nodes.push({
client: {
sendRawTransaction: sendRawTransaction
}
});
bitcoind.sendTransaction(txhex, function(err) {
should.exist(err);
err.should.be.an.instanceof(errors.RPCError);
});
});
it('will send to client and get hash', function() {
var bitcoind = new BitcoinService(baseConfig);
var sendRawTransaction = sinon.stub().callsArgWith(2, null, {
result: tx.hash
});
bitcoind.nodes.push({
client: {
sendRawTransaction: sendRawTransaction
}
});
bitcoind.sendTransaction(txhex, function(err, hash) {
if (err) {
return done(err);
}
hash.should.equal(tx.hash);
});
});
it('will send to client with absurd fees and get hash', function() {
var bitcoind = new BitcoinService(baseConfig);
var sendRawTransaction = sinon.stub().callsArgWith(2, null, {
result: tx.hash
});
bitcoind.nodes.push({
client: {
sendRawTransaction: sendRawTransaction
}
});
bitcoind.sendTransaction(txhex, {allowAbsurdFees: true}, function(err, hash) {
if (err) {
return done(err);
}
hash.should.equal(tx.hash);
});
});
it('missing callback will throw error', function() {
var bitcoind = new BitcoinService(baseConfig);
var sendRawTransaction = sinon.stub().callsArgWith(2, null, {
result: tx.hash
});
bitcoind.nodes.push({
client: {
sendRawTransaction: sendRawTransaction
}
});
var transaction = bitcore.Transaction();
(function() {
bitcoind.sendTransaction(transaction);
}).should.throw(Error);
});
});
describe('#getRawTransaction', function() {
it('will give rpc error', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getRawTransaction = sinon.stub().callsArgWith(1, {message: 'error', code: -1});
bitcoind.nodes.push({
client: {
getRawTransaction: getRawTransaction
}
});
bitcoind.getRawTransaction('txid', function(err) {
should.exist(err);
err.should.be.an.instanceof(errors.RPCError);
done();
});
});
it('will try all nodes', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.tryAllInterval = 1;
var getRawTransactionWithError = sinon.stub().callsArgWith(1, {message: 'error', code: -1});
var getRawTransaction = sinon.stub().callsArgWith(1, null, {
result: txhex
});
bitcoind.nodes.push({
client: {
getRawTransaction: getRawTransactionWithError
}
});
bitcoind.nodes.push({
client: {
getRawTransaction: getRawTransactionWithError
}
});
bitcoind.nodes.push({
client: {
getRawTransaction: getRawTransaction
}
});
bitcoind.getRawTransaction('txid', function(err, tx) {
if (err) {
return done(err);
}
should.exist(tx);
tx.should.be.an.instanceof(Buffer);
done();
});
});
it('will get from cache', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getRawTransaction = sinon.stub().callsArgWith(1, null, {
result: txhex
});
bitcoind.nodes.push({
client: {
getRawTransaction: getRawTransaction
}
});
bitcoind.getRawTransaction('txid', function(err, tx) {
if (err) {
return done(err);
}
should.exist(tx);
tx.should.be.an.instanceof(Buffer);
bitcoind.getRawTransaction('txid', function(err, tx) {
should.exist(tx);
tx.should.be.an.instanceof(Buffer);
getRawTransaction.callCount.should.equal(1);
done();
});
});
});
});
describe('#getTransaction', function() {
it('will give rpc error', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getRawTransaction = sinon.stub().callsArgWith(1, {message: 'error', code: -1});
bitcoind.nodes.push({
client: {
getRawTransaction: getRawTransaction
}
});
bitcoind.getTransaction('txid', function(err) {
should.exist(err);
err.should.be.an.instanceof(errors.RPCError);
done();
});
});
it('will try all nodes', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.tryAllInterval = 1;
var getRawTransactionWithError = sinon.stub().callsArgWith(1, {message: 'error', code: -1});
var getRawTransaction = sinon.stub().callsArgWith(1, null, {
result: txhex
});
bitcoind.nodes.push({
client: {
getRawTransaction: getRawTransactionWithError
}
});
bitcoind.nodes.push({
client: {
getRawTransaction: getRawTransactionWithError
}
});
bitcoind.nodes.push({
client: {
getRawTransaction: getRawTransaction
}
});
bitcoind.getTransaction('txid', function(err, tx) {
if (err) {
return done(err);
}
should.exist(tx);
tx.should.be.an.instanceof(bitcore.Transaction);
done();
});
});
it('will get from cache', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getRawTransaction = sinon.stub().callsArgWith(1, null, {
result: txhex
});
bitcoind.nodes.push({
client: {
getRawTransaction: getRawTransaction
}
});
bitcoind.getTransaction('txid', function(err, tx) {
if (err) {
return done(err);
}
should.exist(tx);
tx.should.be.an.instanceof(bitcore.Transaction);
bitcoind.getTransaction('txid', function(err, tx) {
should.exist(tx);
tx.should.be.an.instanceof(bitcore.Transaction);
getRawTransaction.callCount.should.equal(1);
done();
});
});
});
});
describe('#getDetailedTransaction', function() {
var txBuffer = new Buffer('01000000016f95980911e01c2c664b3e78299527a47933aac61a515930a8fe0213d1ac9abe01000000da0047304402200e71cda1f71e087c018759ba3427eb968a9ea0b1decd24147f91544629b17b4f0220555ee111ed0fc0f751ffebf097bdf40da0154466eb044e72b6b3dcd5f06807fa01483045022100c86d6c8b417bff6cc3bbf4854c16bba0aaca957e8f73e19f37216e2b06bb7bf802205a37be2f57a83a1b5a8cc511dc61466c11e9ba053c363302e7b99674be6a49fc0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9148a31d53a448c18996e81ce67811e5fb7da21e4468738c9d6f90000000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000', 'hex');
var info = {
blockHash: '00000000000ec715852ea2ecae4dc8563f62d603c820f81ac284cd5be0a944d6',
height: 530482,
timestamp: 1439559434000,
buffer: txBuffer
};
var rpcRawTransaction = {
hex: txBuffer.toString('hex'),
blockhash: info.blockHash,
height: info.height,
version: 1,
locktime: 411451,
time: info.timestamp,
vin: [
{
valueSat: 110,
address: 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW',
txid: '3d003413c13eec3fa8ea1fe8bbff6f40718c66facffe2544d7516c9e2900cac2',
sequence: 0xFFFFFFFF,
vout: 0,
scriptSig: {
hex: 'scriptSigHex',
asm: 'scriptSigAsm'
}
}
],
vout: [
{
spentTxId: '4316b98e7504073acd19308b4b8c9f4eeb5e811455c54c0ebfe276c0b1eb6315',
spentIndex: 2,
spentHeight: 100,
valueSat: 100,
scriptPubKey: {
hex: '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac',
asm: 'OP_DUP OP_HASH160 0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc OP_EQUALVERIFY OP_CHECKSIG',
addresses: ['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW']
}
}
]
};
it('should give a transaction with height and timestamp', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.nodes.push({
client: {
getRawTransaction: sinon.stub().callsArgWith(2, {code: -1, message: 'Test error'})
}
});
var txid = '2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f';
bitcoind.getDetailedTransaction(txid, function(err) {
should.exist(err);
err.should.be.instanceof(errors.RPCError);
done();
});
});
it('should give a transaction with all properties', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getRawTransaction = sinon.stub().callsArgWith(2, null, {
result: rpcRawTransaction
});
bitcoind.nodes.push({
client: {
getRawTransaction: getRawTransaction
}
});
var txid = '2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f';
function checkTx(tx) {
/* jshint maxstatements: 30 */
should.exist(tx);
should.not.exist(tx.coinbase);
should.equal(tx.hex, txBuffer.toString('hex'));
should.equal(tx.blockHash, '00000000000ec715852ea2ecae4dc8563f62d603c820f81ac284cd5be0a944d6');
should.equal(tx.height, 530482);
should.equal(tx.blockTimestamp, 1439559434000);
should.equal(tx.version, 1);
should.equal(tx.locktime, 411451);
should.equal(tx.feeSatoshis, 10);
should.equal(tx.inputSatoshis, 110);
should.equal(tx.outputSatoshis, 100);
should.equal(tx.hash, txid);
var input = tx.inputs[0];
should.equal(input.prevTxId, '3d003413c13eec3fa8ea1fe8bbff6f40718c66facffe2544d7516c9e2900cac2');
should.equal(input.outputIndex, 0);
should.equal(input.satoshis, 110);
should.equal(input.sequence, 0xFFFFFFFF);
should.equal(input.script, 'scriptSigHex');
should.equal(input.scriptAsm, 'scriptSigAsm');
should.equal(input.address, 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW');
var output = tx.outputs[0];
should.equal(output.satoshis, 100);
should.equal(output.script, '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac');
should.equal(output.scriptAsm, 'OP_DUP OP_HASH160 0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc OP_EQUALVERIFY OP_CHECKSIG');
should.equal(output.address, 'mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW');
should.equal(output.spentTxId, '4316b98e7504073acd19308b4b8c9f4eeb5e811455c54c0ebfe276c0b1eb6315');
should.equal(output.spentIndex, 2);
should.equal(output.spentHeight, 100);
}
bitcoind.getDetailedTransaction(txid, function(err, tx) {
if (err) {
return done(err);
}
checkTx(tx);
bitcoind.getDetailedTransaction(txid, function(err, tx) {
if (err) {
return done(err);
}
checkTx(tx);
getRawTransaction.callCount.should.equal(1);
done();
});
});
});
it('should set coinbase to true', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var rawTransaction = JSON.parse((JSON.stringify(rpcRawTransaction)));
delete rawTransaction.vin[0];
rawTransaction.vin = [
{
coinbase: 'abcdef'
}
];
bitcoind.nodes.push({
client: {
getRawTransaction: sinon.stub().callsArgWith(2, null, {
result: rawTransaction
})
}
});
var txid = '2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f';
bitcoind.getDetailedTransaction(txid, function(err, tx) {
should.exist(tx);
should.equal(tx.coinbase, true);
done();
});
});
it('will not include address if address length is zero', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var rawTransaction = JSON.parse((JSON.stringify(rpcRawTransaction)));
rawTransaction.vout[0].scriptPubKey.addresses = [];
bitcoind.nodes.push({
client: {
getRawTransaction: sinon.stub().callsArgWith(2, null, {
result: rawTransaction
})
}
});
var txid = '2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f';
bitcoind.getDetailedTransaction(txid, function(err, tx) {
should.exist(tx);
should.equal(tx.outputs[0].address, null);
done();
});
});
it('will not include address if address length is greater than 1', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var rawTransaction = JSON.parse((JSON.stringify(rpcRawTransaction)));
rawTransaction.vout[0].scriptPubKey.addresses = ['one', 'two'];
bitcoind.nodes.push({
client: {
getRawTransaction: sinon.stub().callsArgWith(2, null, {
result: rawTransaction
})
}
});
var txid = '2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f';
bitcoind.getDetailedTransaction(txid, function(err, tx) {
should.exist(tx);
should.equal(tx.outputs[0].address, null);
done();
});
});
it('will handle scriptPubKey.addresses not being set', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var rawTransaction = JSON.parse((JSON.stringify(rpcRawTransaction)));
delete rawTransaction.vout[0].scriptPubKey['addresses'];
bitcoind.nodes.push({
client: {
getRawTransaction: sinon.stub().callsArgWith(2, null, {
result: rawTransaction
})
}
});
var txid = '2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f';
bitcoind.getDetailedTransaction(txid, function(err, tx) {
should.exist(tx);
should.equal(tx.outputs[0].address, null);
done();
});
});
it('will not include script if input missing scriptSig or coinbase', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var rawTransaction = JSON.parse((JSON.stringify(rpcRawTransaction)));
delete rawTransaction.vin[0].scriptSig;
delete rawTransaction.vin[0].coinbase;
bitcoind.nodes.push({
client: {
getRawTransaction: sinon.stub().callsArgWith(2, null, {
result: rawTransaction
})
}
});
var txid = '2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f';
bitcoind.getDetailedTransaction(txid, function(err, tx) {
should.exist(tx);
should.equal(tx.inputs[0].script, null);
done();
});
});
it('will set height to -1 if missing height', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var rawTransaction = JSON.parse((JSON.stringify(rpcRawTransaction)));
delete rawTransaction.height;
bitcoind.nodes.push({
client: {
getRawTransaction: sinon.stub().callsArgWith(2, null, {
result: rawTransaction
})
}
});
var txid = '2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f';
bitcoind.getDetailedTransaction(txid, function(err, tx) {
should.exist(tx);
should.equal(tx.height, -1);
done();
});
});
});
describe('#getBestBlockHash', function() {
it('will give rpc error', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBestBlockHash = sinon.stub().callsArgWith(0, {message: 'error', code: -1});
bitcoind.nodes.push({
client: {
getBestBlockHash: getBestBlockHash
}
});
bitcoind.getBestBlockHash(function(err) {
should.exist(err);
err.should.be.an.instanceof(errors.RPCError);
done();
});
});
it('will call client getInfo and give result', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getBestBlockHash = sinon.stub().callsArgWith(0, null, {
result: 'besthash'
});
bitcoind.nodes.push({
client: {
getBestBlockHash: getBestBlockHash
}
});
bitcoind.getBestBlockHash(function(err, hash) {
if (err) {
return done(err);
}
should.exist(hash);
hash.should.equal('besthash');
done();
});
});
});
describe('#getSpentInfo', function() {
it('will give rpc error', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getSpentInfo = sinon.stub().callsArgWith(1, {message: 'error', code: -1});
bitcoind.nodes.push({
client: {
getSpentInfo: getSpentInfo
}
});
bitcoind.getSpentInfo({}, function(err) {
should.exist(err);
err.should.be.an.instanceof(errors.RPCError);
done();
});
});
it('will empty object when not found', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getSpentInfo = sinon.stub().callsArgWith(1, {message: 'test', code: -5});
bitcoind.nodes.push({
client: {
getSpentInfo: getSpentInfo
}
});
bitcoind.getSpentInfo({}, function(err, info) {
should.not.exist(err);
info.should.deep.equal({});
done();
});
});
it('will call client getSpentInfo and give result', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getSpentInfo = sinon.stub().callsArgWith(1, null, {
result: {
txid: 'txid',
index: 10,
height: 101
}
});
bitcoind.nodes.push({
client: {
getSpentInfo: getSpentInfo
}
});
bitcoind.getSpentInfo({}, function(err, info) {
if (err) {
return done(err);
}
info.txid.should.equal('txid');
info.index.should.equal(10);
info.height.should.equal(101);
done();
});
});
});
describe('#getInfo', function() {
it('will give rpc error', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var getInfo = sinon.stub().callsArgWith(0, {message: 'error', code: -1});
bitcoind.nodes.push({
client: {
getInfo: getInfo
}
});
bitcoind.getInfo(function(err) {
should.exist(err);
err.should.be.an.instanceof(errors.RPCError);
done();
});
});
it('will call client getInfo and give result', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.node.getNetworkName = sinon.stub().returns('testnet');
var getInfo = sinon.stub().callsArgWith(0, null, {
result: {
version: 1,
protocolversion: 1,
blocks: 1,
timeoffset: 1,
connections: 1,
proxy: '',
difficulty: 1,
testnet: true,
relayfee: 10,
errors: ''
}
});
bitcoind.nodes.push({
client: {
getInfo: getInfo
}
});
bitcoind.getInfo(function(err, info) {
if (err) {
return done(err);
}
should.exist(info);
should.equal(info.version, 1);
should.equal(info.protocolVersion, 1);
should.equal(info.blocks, 1);
should.equal(info.timeOffset, 1);
should.equal(info.connections, 1);
should.equal(info.proxy, '');
should.equal(info.difficulty, 1);
should.equal(info.testnet, true);
should.equal(info.relayFee, 10);
should.equal(info.errors, '');
info.network.should.equal('testnet');
done();
});
});
});
describe('#generateBlock', function() {
it('will give rpc error', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var generate = sinon.stub().callsArgWith(1, {message: 'error', code: -1});
bitcoind.nodes.push({
client: {
generate: generate
}
});
bitcoind.generateBlock(10, function(err) {
should.exist(err);
err.should.be.an.instanceof(errors.RPCError);
done();
});
});
it('will call client generate and give result', function(done) {
var bitcoind = new BitcoinService(baseConfig);
var generate = sinon.stub().callsArgWith(1, null, {
result: ['hash']
});
bitcoind.nodes.push({
client: {
generate: generate
}
});
bitcoind.generateBlock(10, function(err, hashes) {
if (err) {
return done(err);
}
hashes.length.should.equal(1);
hashes[0].should.equal('hash');
done();
});
});
});
describe('#stop', function() {
it('will callback if spawn is not set', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.stop(done);
});
it('will exit spawned process', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.spawn = {};
bitcoind.spawn.process = new EventEmitter();
bitcoind.spawn.process.kill = sinon.stub();
bitcoind.stop(done);
bitcoind.spawn.process.kill.callCount.should.equal(1);
bitcoind.spawn.process.kill.args[0][0].should.equal('SIGINT');
bitcoind.spawn.process.emit('exit', 0);
});
it('will give error with non-zero exit status code', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.spawn = {};
bitcoind.spawn.process = new EventEmitter();
bitcoind.spawn.process.kill = sinon.stub();
bitcoind.stop(function(err) {
err.should.be.instanceof(Error);
err.code.should.equal(1);
done();
});
bitcoind.spawn.process.kill.callCount.should.equal(1);
bitcoind.spawn.process.kill.args[0][0].should.equal('SIGINT');
bitcoind.spawn.process.emit('exit', 1);
});
it('will stop after timeout', function(done) {
var bitcoind = new BitcoinService(baseConfig);
bitcoind.shutdownTimeout = 300;
bitcoind.spawn = {};
bitcoind.spawn.process = new EventEmitter();
bitcoind.spawn.process.kill = sinon.stub();
bitcoind.stop(function(err) {
err.should.be.instanceof(Error);
done();
});
bitcoind.spawn.process.kill.callCount.should.equal(1);
bitcoind.spawn.process.kill.args[0][0].should.equal('SIGINT');
});
});
});