test: update unit tests, refactoring and cleanup

This commit is contained in:
Braydon Fuller 2016-04-13 11:13:44 -04:00
parent 848dc29777
commit 890b38744d
21 changed files with 2405 additions and 5002 deletions

View File

@ -14,9 +14,5 @@ node_js:
- "v0.12.7" - "v0.12.7"
- "v4" - "v4"
script: script:
- _mocha -R spec regtest/p2p.js - npm run regtest
- _mocha -R spec regtest/bitcoind.js - npm run test
- _mocha -R spec regtest/cluster.js
- _mocha -R spec regtest/node.js
- _mocha -R spec --recursive

View File

@ -3,23 +3,10 @@
var createError = require('errno').create; var createError = require('errno').create;
var BitcoreNodeError = createError('BitcoreNodeError'); var BitcoreNodeError = createError('BitcoreNodeError');
var NoOutputs = createError('NoOutputs', BitcoreNodeError);
var NoOutput = createError('NoOutput', BitcoreNodeError);
var Wallet = createError('WalletError', BitcoreNodeError); var RPCError = createError('RPCError', BitcoreNodeError);
Wallet.InsufficientFunds = createError('InsufficientFunds', Wallet);
var Consensus = createError('Consensus', BitcoreNodeError);
Consensus.BlockExists = createError('BlockExists', Consensus);
var Transaction = createError('Transaction', BitcoreNodeError);
Transaction.NotFound = createError('NotFound', Transaction);
module.exports = { module.exports = {
Error: BitcoreNodeError, Error: BitcoreNodeError,
NoOutputs: NoOutputs, RPCError: RPCError
NoOutput: NoOutput,
Wallet: Wallet,
Consensus: Consensus,
Transaction: Transaction
}; };

View File

@ -7,6 +7,7 @@ var path = require('path');
* or default locations. * or default locations.
* @param {Object} options * @param {Object} options
* @param {String} options.network - "testnet" or "livenet" * @param {String} options.network - "testnet" or "livenet"
* @param {String} options.datadir - Absolute path to bitcoin database directory
*/ */
function getDefaultBaseConfig(options) { function getDefaultBaseConfig(options) {
if (!options) { if (!options) {

View File

@ -14,7 +14,9 @@ var $ = bitcore.util.preconditions;
var _ = bitcore.deps._; var _ = bitcore.deps._;
var index = require('../'); var index = require('../');
var errors = index.errors;
var log = index.log; var log = index.log;
var utils = require('../utils');
var Service = require('../service'); var Service = require('../service');
var Transaction = require('../transaction'); var Transaction = require('../transaction');
@ -45,6 +47,12 @@ function Bitcoin(options) {
this.subscriptions.transaction = []; this.subscriptions.transaction = [];
this.subscriptions.block = []; this.subscriptions.block = [];
// limits
this.maxAddressesQuery = options.maxAddressesQuery || Bitcoin.DEFAULT_MAX_ADDRESSES_QUERY;
// try all interval
this.tryAllInterval = options.tryAllInterval || Bitcoin.DEFAULT_TRY_ALL_INTERVAL;
// available bitcoind nodes // available bitcoind nodes
this._initClients(); this._initClients();
} }
@ -52,7 +60,21 @@ util.inherits(Bitcoin, Service);
Bitcoin.dependencies = []; Bitcoin.dependencies = [];
Bitcoin.DEFAULT_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n' + 'addressindex=1\n' + 'server=1\n'; Bitcoin.DEFAULT_MAX_ADDRESSES_QUERY = 10000;
Bitcoin.DEFAULT_TRY_ALL_INTERVAL = 1000;
Bitcoin.DEFAULT_CONFIG_SETTINGS = {
server: 1,
whitelist: '127.0.0.1',
txindex: 1,
addressindex: 1,
timestampindex: 1,
spentindex: 1,
zmqpubrawtx: 'tcp://127.0.0.1:28332',
zmqpubhashblock: 'tcp://127.0.0.1:28332',
rpcallowip: '127.0.0.1',
rpcuser: 'bitcoin',
rpcpassword: 'local321'
};
Bitcoin.prototype._initCaches = function() { Bitcoin.prototype._initCaches = function() {
// caches valid until there is a new block // caches valid until there is a new block
@ -70,8 +92,8 @@ Bitcoin.prototype._initCaches = function() {
this.blockHeaderCache = LRU(288); this.blockHeaderCache = LRU(288);
this.zmqKnownTransactions = LRU(50); this.zmqKnownTransactions = LRU(50);
this.zmqKnownBlocks = LRU(50); this.zmqKnownBlocks = LRU(50);
this.zmqLastBlock = 0; this.lastTip = 0;
this.zmqUpdateTipTimeout = false; this.lastTipTimeout = false;
}; };
Bitcoin.prototype._initClients = function() { Bitcoin.prototype._initClients = function() {
@ -149,6 +171,15 @@ Bitcoin.prototype.unsubscribe = function(name, emitter) {
} }
}; };
Bitcoin.prototype._getDefaultConfig = function() {
var config = '';
var defaults = Bitcoin.DEFAULT_CONFIG_SETTINGS;
for(var key in defaults) {
config += key + '=' + defaults[key] + '\n';
}
return config;
};
Bitcoin.prototype._loadSpawnConfiguration = function(node) { Bitcoin.prototype._loadSpawnConfiguration = function(node) {
/* jshint maxstatements: 25 */ /* jshint maxstatements: 25 */
@ -169,6 +200,11 @@ Bitcoin.prototype._loadSpawnConfiguration = function(node) {
mkdirp.sync(spawnOptions.datadir); mkdirp.sync(spawnOptions.datadir);
} }
if (!fs.existsSync(configPath)) {
var defaultConfig = this._getDefaultConfig();
fs.writeFileSync(configPath, defaultConfig);
}
var file = fs.readFileSync(configPath); var file = fs.readFileSync(configPath);
var unparsed = file.toString().split('\n'); var unparsed = file.toString().split('\n');
for(var i = 0; i < unparsed.length; i++) { for(var i = 0; i < unparsed.length; i++) {
@ -187,6 +223,11 @@ Bitcoin.prototype._loadSpawnConfiguration = function(node) {
var spawnConfig = this.spawn.config; var spawnConfig = this.spawn.config;
this._checkConfigIndexes(spawnConfig, node);
};
Bitcoin.prototype._checkConfigIndexes = function(spawnConfig, node) {
$.checkState( $.checkState(
spawnConfig.txindex && spawnConfig.txindex === 1, spawnConfig.txindex && spawnConfig.txindex === 1,
'"txindex" option is required in order to use transaction query features of bitcore-node. ' + '"txindex" option is required in order to use transaction query features of bitcore-node. ' +
@ -233,7 +274,6 @@ Bitcoin.prototype._loadSpawnConfiguration = function(node) {
'of bitcore-node services will start.'); 'of bitcore-node services will start.');
node._reindex = true; node._reindex = true;
} }
}; };
Bitcoin.prototype._resetCaches = function() { Bitcoin.prototype._resetCaches = function() {
@ -245,11 +285,11 @@ Bitcoin.prototype._resetCaches = function() {
}; };
Bitcoin.prototype._tryAll = function(func, callback) { Bitcoin.prototype._tryAll = function(func, callback) {
async.retry({times: this.nodes.length, interval: 1000}, func, callback); async.retry({times: this.nodes.length, interval: this.tryAllInterval || 1000}, func, callback);
}; };
Bitcoin.prototype._wrapRPCError = function(errObj) { Bitcoin.prototype._wrapRPCError = function(errObj) {
var err = new Error(errObj.message); var err = new errors.RPCError(errObj.message);
err.code = errObj.code; err.code = errObj.code;
return err; return err;
}; };
@ -274,11 +314,11 @@ Bitcoin.prototype._initChain = function(callback) {
return callback(self._wrapRPCError(err)); return callback(self._wrapRPCError(err));
} }
var blockhash = response.result; var blockhash = response.result;
self.getBlock(blockhash, function(err, block) { self.getRawBlock(blockhash, function(err, blockBuffer) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
self.genesisBuffer = block.toBuffer(); self.genesisBuffer = blockBuffer;
self.emit('ready'); self.emit('ready');
log.info('Bitcoin Daemon Ready'); log.info('Bitcoin Daemon Ready');
callback(); callback();
@ -303,55 +343,72 @@ Bitcoin.prototype._getNetworkOption = function() {
Bitcoin.prototype._zmqBlockHandler = function(node, message) { Bitcoin.prototype._zmqBlockHandler = function(node, message) {
var self = this; var self = this;
function updateChain() { // Update the current chain tip
var hex = message.toString('hex'); self._rapidProtectedUpdateTip(node, message);
if (hex !== self.tiphash) {
self._resetCaches();
self.tiphash = message.toString('hex');
node.client.getBlock(self.tiphash, function(err, response) {
if (err) {
return log.error(self._wrapRPCError(err));
}
self.height = response.result.height;
$.checkState(self.height >= 0);
self.emit('tip', self.height);
});
if(!self.node.stopping) {
self.syncPercentage(function(err, percentage) {
if (err) {
return log.error(err);
}
if (Math.round(percentage) >= 100) {
self.emit('synced', self.height);
}
log.info('Bitcoin Height:', self.height, 'Percentage:', percentage.toFixed(2));
});
}
}
}
// Prevent a rapid succession of tip updates
if (new Date() - self.zmqLastBlock > 1000) {
self.zmqLastBlock = new Date();
updateChain();
} else {
clearTimeout(self.zmqUpdateTipTimeout);
self.zmqUpdateTipTimeout = setTimeout(function() {
updateChain();
}, 1000);
}
// Notify block subscribers // Notify block subscribers
var id = message.toString('binary'); var id = message.toString('binary');
if (!self.zmqKnownBlocks[id]) { if (!self.zmqKnownBlocks.get(id)) {
self.zmqKnownBlocks[id] = true; self.zmqKnownBlocks.set(id, true);
self.emit('block', message); self.emit('block', message);
for (var i = 0; i < this.subscriptions.block.length; i++) { for (var i = 0; i < this.subscriptions.block.length; i++) {
this.subscriptions.block[i].emit('bitcoind/block', message.toString('hex')); this.subscriptions.block[i].emit('bitcoind/block', message.toString('hex'));
} }
}
};
Bitcoin.prototype._rapidProtectedUpdateTip = function(node, message) {
var self = this;
// Prevent a rapid succession of tip updates
if (new Date() - self.lastTip > 1000) {
self.lastTip = new Date();
self._updateTip(node, message);
} else {
clearTimeout(self.lastTipTimeout);
self.lastTipTimeout = setTimeout(function() {
self._updateTip(node, message);
}, 1000);
}
};
Bitcoin.prototype._updateTip = function(node, message) {
var self = this;
var hex = message.toString('hex');
if (hex !== self.tiphash) {
self.tiphash = message.toString('hex');
// reset block valid caches
self._resetCaches();
node.client.getBlock(self.tiphash, function(err, response) {
if (err) {
var error = self._wrapRPCError(err);
log.error(error);
self.emit('error', error);
} else {
self.height = response.result.height;
$.checkState(self.height >= 0);
self.emit('tip', self.height);
}
});
if(!self.node.stopping) {
self.syncPercentage(function(err, percentage) {
if (err) {
log.error(err);
self.emit('error', err);
} else {
if (Math.round(percentage) >= 100) {
self.emit('synced', self.height);
}
log.info('Bitcoin Height:', self.height, 'Percentage:', percentage.toFixed(2));
}
});
}
} }
}; };
@ -359,16 +416,15 @@ Bitcoin.prototype._zmqBlockHandler = function(node, message) {
Bitcoin.prototype._zmqTransactionHandler = function(node, message) { Bitcoin.prototype._zmqTransactionHandler = function(node, message) {
var self = this; var self = this;
var id = message.toString('binary'); var id = message.toString('binary');
if (!self.zmqKnownTransactions[id]) { if (!self.zmqKnownTransactions.get(id)) {
self.zmqKnownTransactions[id] = true; self.zmqKnownTransactions.set(id, true);
self.emit('tx', message); self.emit('tx', message);
}
// Notify transaction subscribers // Notify transaction subscribers
for (var i = 0; i < this.subscriptions.transaction.length; i++) { for (var i = 0; i < this.subscriptions.transaction.length; i++) {
this.subscriptions.transaction[i].emit('bitcoind/transaction', message); this.subscriptions.transaction[i].emit('bitcoind/transaction', message.toString('hex'));
}
} }
}; };
Bitcoin.prototype._subscribeZmqEvents = function(node) { Bitcoin.prototype._subscribeZmqEvents = function(node) {
@ -414,21 +470,24 @@ Bitcoin.prototype._initZmqSubSocket = function(node, zmqUrl) {
Bitcoin.prototype._checkReindex = function(node, callback) { Bitcoin.prototype._checkReindex = function(node, callback) {
var self = this; var self = this;
var interval;
function finish(err) {
clearInterval(interval);
callback(err);
}
if (node._reindex) { if (node._reindex) {
var interval = setInterval(function() { interval = setInterval(function() {
node.client.syncPercentage(function(err, percentSynced) { node.client.syncPercentage(function(err, percentSynced) {
if (err) { if (err) {
return log.error(self._wrapRPCError(err)); return finish(self._wrapRPCError(err));
} }
log.info('Bitcoin Core Daemon Reindex Percentage: ' + percentSynced.toFixed(2)); log.info('Bitcoin Core Daemon Reindex Percentage: ' + percentSynced.toFixed(2));
if (Math.round(percentSynced) >= 100) { if (Math.round(percentSynced) >= 100) {
node._reindex = false; node._reindex = false;
callback(); finish();
clearInterval(interval);
} }
}); });
}, self._reindexWait); }, self._reindexWait);
} else { } else {
callback(); callback();
} }
@ -787,11 +846,15 @@ Bitcoin.prototype.getAddressTxids = function(addressArg, options, callback) {
}; };
Bitcoin.prototype._getConfirmationsDetail = function(transaction) { Bitcoin.prototype._getConfirmationsDetail = function(transaction) {
$.checkState(this.height > 0, 'current height is unknown');
var confirmations = 0; var confirmations = 0;
if (transaction.__height >= 0) { if (transaction.__height >= 0) {
confirmations = this.height - transaction.__height + 1; confirmations = this.height - transaction.__height + 1;
} }
return confirmations; if (confirmations < 0) {
log.warn('Negative confirmations calculated for transaction:', transaction.hash);
}
return Math.max(0, confirmations);
}; };
Bitcoin.prototype._getAddressDetailsForTransaction = function(transaction, addressStrings) { Bitcoin.prototype._getAddressDetailsForTransaction = function(transaction, addressStrings) {
@ -904,6 +967,7 @@ Bitcoin.prototype._getAddressStrings = function(addresses) {
Bitcoin.prototype._paginateTxids = function(fullTxids, from, to) { Bitcoin.prototype._paginateTxids = function(fullTxids, from, to) {
var txids; var txids;
if (from >= 0 && to >= 0) { if (from >= 0 && to >= 0) {
$.checkState(from < to, '"from" is expected to be less than "to"');
txids = fullTxids.slice(from, to); txids = fullTxids.slice(from, to);
} else { } else {
txids = fullTxids; txids = fullTxids;
@ -933,7 +997,11 @@ Bitcoin.prototype.getAddressHistory = function(addressArg, options, callback) {
} }
var totalCount = txids.length; var totalCount = txids.length;
txids = self._paginateTxids(txids, options.from, options.to); try {
txids = self._paginateTxids(txids, options.from, options.to);
} catch(e) {
return callback(e);
}
async.mapSeries( async.mapSeries(
txids, txids,
@ -1304,6 +1372,7 @@ Bitcoin.prototype.getTransaction = function(txid, callback) {
* @param {Function} callback * @param {Function} callback
*/ */
Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, callback) { Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, callback) {
// TODO give response back as standard js object with bitcore tx
var self = this; var self = this;
var tx = self.transactionInfoCache.get(txid); var tx = self.transactionInfoCache.get(txid);
if (tx) { if (tx) {
@ -1408,9 +1477,11 @@ Bitcoin.prototype.generateBlock = function(num, callback) {
*/ */
Bitcoin.prototype.stop = function(callback) { Bitcoin.prototype.stop = function(callback) {
if (this.spawn && this.spawn.process) { if (this.spawn && this.spawn.process) {
this.spawn.process.once('exit', function(err, status) { this.spawn.process.once('exit', function(code) {
if (err) { if (code !== 0) {
return callback(err); var error = new Error('bitcoind spawned process exited with status code: ' + code);
error.code = code;
return callback(error);
} else { } else {
return callback(); return callback();
} }

View File

@ -4,9 +4,6 @@ var async = require('async');
var bitcore = require('bitcore-lib'); var bitcore = require('bitcore-lib');
var Transaction = bitcore.Transaction; var Transaction = bitcore.Transaction;
var index = require('./');
var errors = index.errors;
var MAX_TRANSACTION_LIMIT = 5; var MAX_TRANSACTION_LIMIT = 5;
Transaction.prototype.populateSpentInfo = function(db, options, callback) { Transaction.prototype.populateSpentInfo = function(db, options, callback) {
@ -53,11 +50,13 @@ Transaction.prototype.populateInputs = function(db, poolTransactions, callback)
Transaction.prototype._populateInput = function(db, input, poolTransactions, callback) { Transaction.prototype._populateInput = function(db, input, poolTransactions, callback) {
if (!input.prevTxId || !Buffer.isBuffer(input.prevTxId)) { if (!input.prevTxId || !Buffer.isBuffer(input.prevTxId)) {
return callback(new Error('Input is expected to have prevTxId as a buffer')); return callback(new TypeError('Input is expected to have prevTxId as a buffer'));
} }
var txid = input.prevTxId.toString('hex'); var txid = input.prevTxId.toString('hex');
db.getTransaction(txid, function(err, prevTx) { db.getTransaction(txid, function(err, prevTx) {
if(!prevTx) { if(err) {
return callback(err);
} else if (!prevTx) {
// Check the pool for transaction // Check the pool for transaction
for(var i = 0; i < poolTransactions.length; i++) { for(var i = 0; i < poolTransactions.length; i++) {
if(txid === poolTransactions[i].hash) { if(txid === poolTransactions[i].hash) {
@ -65,25 +64,10 @@ Transaction.prototype._populateInput = function(db, input, poolTransactions, cal
return callback(); return callback();
} }
} }
return callback(new Error('Previous tx ' + input.prevTxId.toString('hex') + ' not found')); return callback(new Error('Previous tx ' + input.prevTxId.toString('hex') + ' not found'));
} else if(err) {
callback(err);
} else {
input.output = prevTx.outputs[input.outputIndex];
callback();
}
});
};
Transaction.prototype._checkSpent = function(db, input, poolTransactions, callback) {
// TODO check and see if another transaction in the pool spent the output
db.isSpentDB(input, function(spent) {
if(spent) {
return callback(new Error('Input already spent'));
} else {
callback();
} }
input.output = prevTx.outputs[input.outputIndex];
callback();
}); });
}; };

View File

@ -32,6 +32,7 @@
"scripts": { "scripts": {
"install": "./scripts/install", "install": "./scripts/install",
"test": "NODE_ENV=test mocha -R spec --recursive", "test": "NODE_ENV=test mocha -R spec --recursive",
"regtest": "./scripts/regtest",
"coverage": "NODE_ENV=test istanbul cover _mocha -- --recursive" "coverage": "NODE_ENV=test istanbul cover _mocha -- --recursive"
}, },
"tags": [ "tags": [

8
scripts/regtest Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
set -e
_mocha -R spec regtest/p2p.js
_mocha -R spec regtest/bitcoind.js
_mocha -R spec regtest/cluster.js
_mocha -R spec regtest/node.js

View File

@ -1,18 +0,0 @@
'use strict';
var should = require('chai').should();
var path = require('path');
var getTarballName = require('../../bin/get-tarball-name');
var execSync = require('child_process').execSync;
describe('#getTarballName', function() {
it('will return the expected tarball name', function() {
var name = getTarballName();
var version = require(path.resolve(__dirname + '../../../package.json')).version;
var platform = process.platform;
var arch = execSync(path.resolve(__dirname) + '/../../bin/variables.sh arch');
var abi = process.versions.modules;
var expected = 'libbitcoind-' + version + '-node' + abi + '-' + platform + '-' + arch + '.tgz';
name.should.equal(expected);
});
});

View File

@ -1,17 +1,23 @@
#testnet=1 #testnet=1
#irc=0 #irc=0
#upnp=0 upnp=0
server=1 server=1
whitelist=127.0.0.1 whitelist=127.0.0.1
txindex=1 txindex=1
addressindex=1
timestampindex=1
spentindex=1
dbcache=8192
checkblocks=144
maxuploadtarget=1024
zmqpubrawtx=tcp://127.0.0.1:28332
zmqpubhashblock=tcp://127.0.0.1:28332
# listen on different ports
port=20000 port=20000
rpcport=50001
rpcallowip=127.0.0.1 rpcallowip=127.0.0.1
rpcuser=bitcoin rpcuser=bitcoin
rpcpassword=local321 rpcpassword=local321

View File

@ -0,0 +1,11 @@
server=1
whitelist=127.0.0.1
txindex=1
addressindex=1
timestampindex=1
spentindex=1
zmqpubrawtx=tcp://127.0.0.1:28332
zmqpubhashblock=tcp://127.0.0.1:28332
rpcallowip=127.0.0.1
rpcuser=bitcoin
rpcpassword=local321

View File

@ -1,19 +0,0 @@
'use strict';
var should = require('chai').should();
var index = require('..');
describe('Index', function() {
describe('#nodeVersionCheck', function() {
it('will throw informative error message with incompatible Node.js version 4.1.2', function() {
(function() {
index.nodeVersionCheck('4.1.2', '>=0.12.0 <1');
}).should.throw('Node.js version');
});
it('will throw informative error message with incompatible Node.js version 0.10.40', function() {
(function() {
index.nodeVersionCheck('4.1.2', '>=0.12.0 <1');
}).should.throw('Node.js version');
});
});
});

View File

@ -1,6 +1,7 @@
'use strict'; 'use strict';
var should = require('chai').should(); var should = require('chai').should();
var path = require('path');
var defaultBaseConfig = require('../../lib/scaffold/default-base-config'); var defaultBaseConfig = require('../../lib/scaffold/default-base-config');
describe('#defaultBaseConfig', function() { describe('#defaultBaseConfig', function() {
@ -9,29 +10,19 @@ describe('#defaultBaseConfig', function() {
var home = process.env.HOME; var home = process.env.HOME;
var info = defaultBaseConfig(); var info = defaultBaseConfig();
info.path.should.equal(cwd); info.path.should.equal(cwd);
info.config.datadir.should.equal(home + '/.bitcoin');
info.config.network.should.equal('livenet'); info.config.network.should.equal('livenet');
info.config.port.should.equal(3001); info.config.port.should.equal(3001);
info.config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']); info.config.services.should.deep.equal(['bitcoind', 'web']);
var bitcoind = info.config.servicesConfig.bitcoind;
bitcoind.spawn.datadir.should.equal(home + '/.bitcoin');
bitcoind.spawn.exec.should.equal(path.resolve(__dirname, '../../bin/bitcoind'));
}); });
it('be able to specify a network', function() { it('be able to specify a network', function() {
var cwd = process.cwd();
var home = process.env.HOME;
var info = defaultBaseConfig({network: 'testnet'}); var info = defaultBaseConfig({network: 'testnet'});
info.path.should.equal(cwd);
info.config.datadir.should.equal(home + '/.bitcoin');
info.config.network.should.equal('testnet'); info.config.network.should.equal('testnet');
info.config.port.should.equal(3001);
info.config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']);
}); });
it('be able to specify a datadir', function() { it('be able to specify a datadir', function() {
var cwd = process.cwd();
var home = process.env.HOME;
var info = defaultBaseConfig({datadir: './data2', network: 'testnet'}); var info = defaultBaseConfig({datadir: './data2', network: 'testnet'});
info.path.should.equal(cwd); info.config.servicesConfig.bitcoind.spawn.datadir.should.equal('./data2');
info.config.datadir.should.equal('./data2');
info.config.network.should.equal('testnet');
info.config.port.should.equal(3001);
info.config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']);
}); });
}); });

View File

@ -1,21 +1,29 @@
'use strict'; 'use strict';
var path = require('path');
var should = require('chai').should(); var should = require('chai').should();
var sinon = require('sinon'); var sinon = require('sinon');
var proxyquire = require('proxyquire'); var proxyquire = require('proxyquire');
describe('#defaultConfig', function() { describe('#defaultConfig', function() {
var expectedExecPath = path.resolve(__dirname, '../../bin/bitcoind');
it('will return expected configuration', function() { it('will return expected configuration', function() {
var config = JSON.stringify({ var config = JSON.stringify({
datadir: process.env.HOME + '/.bitcore/data',
network: 'livenet', network: 'livenet',
port: 3001, port: 3001,
services: [ services: [
'bitcoind', 'bitcoind',
'db',
'address',
'web' 'web'
] ],
servicesConfig: {
bitcoind: {
spawn: {
datadir: process.env.HOME + '/.bitcore/data',
exec: expectedExecPath
}
}
}
}, null, 2); }, null, 2);
var defaultConfig = proxyquire('../../lib/scaffold/default-config', { var defaultConfig = proxyquire('../../lib/scaffold/default-config', {
fs: { fs: {
@ -32,28 +40,35 @@ describe('#defaultConfig', function() {
sync: sinon.stub() sync: sinon.stub()
} }
}); });
var cwd = process.cwd();
var home = process.env.HOME; var home = process.env.HOME;
var info = defaultConfig(); var info = defaultConfig();
info.path.should.equal(home + '/.bitcore'); info.path.should.equal(home + '/.bitcore');
info.config.datadir.should.equal(home + '/.bitcore/data');
info.config.network.should.equal('livenet'); info.config.network.should.equal('livenet');
info.config.port.should.equal(3001); info.config.port.should.equal(3001);
info.config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']); info.config.services.should.deep.equal(['bitcoind', 'web']);
var bitcoind = info.config.servicesConfig.bitcoind;
should.exist(bitcoind);
bitcoind.spawn.datadir.should.equal(home + '/.bitcore/data');
bitcoind.spawn.exec.should.equal(expectedExecPath);
}); });
it('will include additional services', function() { it('will include additional services', function() {
var config = JSON.stringify({ var config = JSON.stringify({
datadir: process.env.HOME + '/.bitcore/data',
network: 'livenet', network: 'livenet',
port: 3001, port: 3001,
services: [ services: [
'bitcoind', 'bitcoind',
'db',
'address',
'web', 'web',
'insight-api', 'insight-api',
'insight-ui' 'insight-ui'
] ],
servicesConfig: {
bitcoind: {
spawn: {
datadir: process.env.HOME + '/.bitcore/data',
exec: expectedExecPath
}
}
}
}, null, 2); }, null, 2);
var defaultConfig = proxyquire('../../lib/scaffold/default-config', { var defaultConfig = proxyquire('../../lib/scaffold/default-config', {
fs: { fs: {
@ -75,16 +90,17 @@ describe('#defaultConfig', function() {
additionalServices: ['insight-api', 'insight-ui'] additionalServices: ['insight-api', 'insight-ui']
}); });
info.path.should.equal(home + '/.bitcore'); info.path.should.equal(home + '/.bitcore');
info.config.datadir.should.equal(home + '/.bitcore/data');
info.config.network.should.equal('livenet'); info.config.network.should.equal('livenet');
info.config.port.should.equal(3001); info.config.port.should.equal(3001);
info.config.services.should.deep.equal([ info.config.services.should.deep.equal([
'bitcoind', 'bitcoind',
'db',
'address',
'web', 'web',
'insight-api', 'insight-api',
'insight-ui' 'insight-ui'
]); ]);
var bitcoind = info.config.servicesConfig.bitcoind;
should.exist(bitcoind);
bitcoind.spawn.datadir.should.equal(home + '/.bitcore/data');
bitcoind.spawn.exec.should.equal(expectedExecPath);
}); });
}); });

View File

@ -3,7 +3,7 @@
var should = require('chai').should(); var should = require('chai').should();
var sinon = require('sinon'); var sinon = require('sinon');
var proxyquire = require('proxyquire'); var proxyquire = require('proxyquire');
var AddressService = require('../../lib/services/address'); var BitcoinService = require('../../lib/services/bitcoind');
describe('#start', function() { describe('#start', function() {
@ -13,8 +13,8 @@ describe('#start', function() {
var node; var node;
var TestNode = function(options) { var TestNode = function(options) {
options.services[0].should.deep.equal({ options.services[0].should.deep.equal({
name: 'address', name: 'bitcoind',
module: AddressService, module: BitcoinService,
config: {} config: {}
}); });
}; };
@ -32,7 +32,7 @@ describe('#start', function() {
path: __dirname, path: __dirname,
config: { config: {
services: [ services: [
'address' 'bitcoind'
], ],
datadir: './data' datadir: './data'
} }
@ -67,8 +67,8 @@ describe('#start', function() {
var node; var node;
var TestNode = function(options) { var TestNode = function(options) {
options.services[0].should.deep.equal({ options.services[0].should.deep.equal({
name: 'address', name: 'bitcoind',
module: AddressService, module: BitcoinService,
config: { config: {
param: 'test' param: 'test'
} }
@ -88,10 +88,10 @@ describe('#start', function() {
path: __dirname, path: __dirname,
config: { config: {
services: [ services: [
'address' 'bitcoind'
], ],
servicesConfig: { servicesConfig: {
'address': { 'bitcoind': {
param: 'test' param: 'test'
} }
}, },

View File

@ -212,110 +212,6 @@ describe('#start', function() {
}); });
}); });
}); });
describe('#spawnChildProcess', function() {
it('should build the appropriate arguments to spawn a child process', function() {
var child = {
unref: function() {}
};
var _process = {
exit: function() {},
env: {
__bitcore_node: false
},
argv: [
'node',
'bitcore-node'
],
cwd: function(){return ''},
pid: 999,
execPath: '/tmp'
};
var fd = {};
var spawn = sinon.stub().returns(child);
var openSync = sinon.stub().returns(fd);
var spawnChildProcess = proxyquire('../../lib/scaffold/start', {
fs: {
openSync: openSync
},
child_process: {
spawn: spawn
}
}).spawnChildProcess;
spawnChildProcess('/tmp', _process);
spawn.callCount.should.equal(1);
spawn.args[0][0].should.equal(_process.execPath);
var expected = [].concat(_process.argv);
expected.shift();
spawn.args[0][1].should.deep.equal(expected);
var cp_opt = {
stdio: ['ignore', fd, fd],
env: _process.env,
cwd: '',
detached: true
};
spawn.args[0][2].should.deep.equal(cp_opt);
openSync.callCount.should.equal(1);
openSync.args[0][0].should.equal('/tmp/bitcore-node.log');
openSync.args[0][1].should.equal('a+');
});
it('should not spawn a new child process if there is already a daemon running', function() {
var _process = {
exit: function() {},
env: {
__bitcore_node: true
},
argv: [
'node',
'bitcore-node'
],
cwd: 'cwd',
pid: 999,
execPath: '/tmp'
};
var spawnChildProcess = proxyquire('../../lib/scaffold/start', {}).spawnChildProcess;
spawnChildProcess('/tmp', _process).should.equal(999);
});
});
describe('daemon', function() {
var sandbox;
var spawn;
var setup;
var registerSync;
var registerExit;
var start = require('../../lib/scaffold/start');
var options = {
config: {
datadir: '/tmp',
daemon: true
}
}
beforeEach(function() {
sandbox = sinon.sandbox.create();
spawn = sandbox.stub(start, 'spawnChildProcess', function() {});
setup = sandbox.stub(start, 'setupServices', function() {});
registerSync = sandbox.stub(start, 'registerSyncHandlers', function() {});
registerExit = sandbox.stub(start, 'registerExitHandlers', function() {});
});
afterEach(function() {
sandbox.restore();
});
it('call spawnChildProcess if there is a config option to do so', function() {
start(options);
registerSync.callCount.should.equal(1);
registerExit.callCount.should.equal(1);
spawn.callCount.should.equal(1);
});
it('not call spawnChildProcess if there is not an option to do so', function() {
options.config.daemon = false;
start(options);
registerSync.callCount.should.equal(1);
registerExit.callCount.should.equal(1);
spawn.callCount.should.equal(0);
});
});
describe('#registerExitHandlers', function() { describe('#registerExitHandlers', function() {
var stub; var stub;
var registerExitHandlers = require('../../lib/scaffold/start').registerExitHandlers; var registerExitHandlers = require('../../lib/scaffold/start').registerExitHandlers;

View File

@ -1,103 +0,0 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var sinon = require('sinon');
var bitcorenode = require('../../../');
var bitcore = require('bitcore-lib');
var Address = bitcore.Address;
var Script = bitcore.Script;
var AddressService = bitcorenode.services.Address;
var Networks = bitcore.Networks;
var encoding = require('../../../lib/services/address/encoding');
var mockdb = {
};
var mocknode = {
network: Networks.testnet,
datadir: 'testdir',
db: mockdb,
services: {
bitcoind: {
on: sinon.stub()
}
}
};
describe('Address Service Encoding', function() {
describe('#encodeSpentIndexSyncKey', function() {
it('will encode to 36 bytes (string)', function() {
var txidBuffer = new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', 'hex');
var key = encoding.encodeSpentIndexSyncKey(txidBuffer, 12);
key.length.should.equal(36);
});
it('will be able to decode encoded value', function() {
var txid = '3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7';
var txidBuffer = new Buffer(txid, 'hex');
var key = encoding.encodeSpentIndexSyncKey(txidBuffer, 12);
var keyBuffer = new Buffer(key, 'binary');
keyBuffer.slice(0, 32).toString('hex').should.equal(txid);
var outputIndex = keyBuffer.readUInt32BE(32);
outputIndex.should.equal(12);
});
});
describe('#_encodeInputKeyMap/#_decodeInputKeyMap roundtrip', function() {
var encoded;
var outputTxIdBuffer = new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', 'hex');
it('encode key', function() {
encoded = encoding.encodeInputKeyMap(outputTxIdBuffer, 13);
});
it('decode key', function() {
var key = encoding.decodeInputKeyMap(encoded);
key.outputTxId.toString('hex').should.equal(outputTxIdBuffer.toString('hex'));
key.outputIndex.should.equal(13);
});
});
describe('#_encodeInputValueMap/#_decodeInputValueMap roundtrip', function() {
var encoded;
var inputTxIdBuffer = new Buffer('3b6bc2939d1a70ce04bc4f619ee32608fbff5e565c1f9b02e4eaa97959c59ae7', 'hex');
it('encode key', function() {
encoded = encoding.encodeInputValueMap(inputTxIdBuffer, 7);
});
it('decode key', function() {
var key = encoding.decodeInputValueMap(encoded);
key.inputTxId.toString('hex').should.equal(inputTxIdBuffer.toString('hex'));
key.inputIndex.should.equal(7);
});
});
describe('#extractAddressInfoFromScript', function() {
it('pay-to-publickey', function() {
var pubkey = new bitcore.PublicKey('022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da');
var script = Script.buildPublicKeyOut(pubkey);
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
info.addressType.should.equal(Address.PayToPublicKeyHash);
info.hashBuffer.toString('hex').should.equal('9674af7395592ec5d91573aa8d6557de55f60147');
});
it('pay-to-publickeyhash', function() {
var script = Script('OP_DUP OP_HASH160 20 0x0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG');
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
info.addressType.should.equal(Address.PayToPublicKeyHash);
info.hashBuffer.toString('hex').should.equal('0000000000000000000000000000000000000000');
});
it('pay-to-scripthash', function() {
var script = Script('OP_HASH160 20 0x0000000000000000000000000000000000000000 OP_EQUAL');
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
info.addressType.should.equal(Address.PayToScriptHash);
info.hashBuffer.toString('hex').should.equal('0000000000000000000000000000000000000000');
});
it('non-address script type', function() {
var buf = new Buffer(40);
buf.fill(0);
var script = Script('OP_RETURN 40 0x' + buf.toString('hex'));
var info = encoding.extractAddressInfoFromScript(script, Networks.livenet);
info.should.equal(false);
});
});
});

View File

@ -1,544 +0,0 @@
'use strict';
var should = require('chai').should();
var sinon = require('sinon');
var bitcore = require('bitcore-lib');
var Transaction = require('../../../lib/transaction');
var AddressHistory = require('../../../lib/services/address/history');
describe('Address Service History', function() {
var address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX';
describe('@constructor', function() {
it('will construct a new instance', function() {
var node = {};
var options = {};
var addresses = [address];
var history = new AddressHistory({
node: node,
options: options,
addresses: addresses
});
history.should.be.instanceof(AddressHistory);
history.node.should.equal(node);
history.options.should.equal(options);
history.addresses.should.equal(addresses);
history.detailedArray.should.deep.equal([]);
});
it('will set addresses an array if only sent a string', function() {
var history = new AddressHistory({
node: {},
options: {},
addresses: address
});
history.addresses.should.deep.equal([address]);
});
});
describe('#get', function() {
it('will give an error if length of addresses is too long', function(done) {
var node = {};
var options = {};
var addresses = [];
for (var i = 0; i < 101; i++) {
addresses.push(address);
}
var history = new AddressHistory({
node: node,
options: options,
addresses: addresses
});
history.maxAddressesQuery = 100;
history.get(function(err) {
should.exist(err);
err.message.match(/Maximum/);
done();
});
});
it('give error from getAddressSummary with one address', function(done) {
var node = {
services: {
address: {
getAddressSummary: sinon.stub().callsArgWith(2, new Error('test'))
}
}
};
var options = {};
var addresses = [address];
var history = new AddressHistory({
node: node,
options: options,
addresses: addresses
});
history.get(function(err) {
should.exist(err);
err.message.should.equal('test');
done();
});
});
it('give error from getAddressSummary with multiple addresses', function(done) {
var node = {
services: {
address: {
getAddressSummary: sinon.stub().callsArgWith(2, new Error('test2'))
}
}
};
var options = {};
var addresses = [address, address];
var history = new AddressHistory({
node: node,
options: options,
addresses: addresses
});
history.get(function(err) {
should.exist(err);
err.message.should.equal('test2');
done();
});
});
it('will query get address summary directly with one address', function(done) {
var txids = [];
var summary = {
txids: txids
};
var node = {
services: {
address: {
getAddressSummary: sinon.stub().callsArgWith(2, null, summary)
}
}
};
var options = {};
var addresses = [address];
var history = new AddressHistory({
node: node,
options: options,
addresses: addresses
});
history._mergeAndSortTxids = sinon.stub();
history._paginateWithDetails = sinon.stub().callsArg(1);
history.get(function() {
history.node.services.address.getAddressSummary.callCount.should.equal(1);
history.node.services.address.getAddressSummary.args[0][0].should.equal(address);
history.node.services.address.getAddressSummary.args[0][1].should.deep.equal({
noBalance: true
});
history._paginateWithDetails.callCount.should.equal(1);
history._paginateWithDetails.args[0][0].should.equal(txids);
history._mergeAndSortTxids.callCount.should.equal(0);
done();
});
});
it('will merge multiple summaries with multiple addresses', function(done) {
var txids = [];
var summary = {
txids: txids
};
var node = {
services: {
address: {
getAddressSummary: sinon.stub().callsArgWith(2, null, summary)
}
}
};
var options = {};
var addresses = [address, address];
var history = new AddressHistory({
node: node,
options: options,
addresses: addresses
});
history._mergeAndSortTxids = sinon.stub().returns(txids);
history._paginateWithDetails = sinon.stub().callsArg(1);
history.get(function() {
history.node.services.address.getAddressSummary.callCount.should.equal(2);
history.node.services.address.getAddressSummary.args[0][0].should.equal(address);
history.node.services.address.getAddressSummary.args[0][1].should.deep.equal({
fullTxList: true,
noBalance: true
});
history._paginateWithDetails.callCount.should.equal(1);
history._paginateWithDetails.args[0][0].should.equal(txids);
history._mergeAndSortTxids.callCount.should.equal(1);
done();
});
});
});
describe('#_paginateWithDetails', function() {
it('slice txids based on "from" and "to" (3 to 30)', function() {
var node = {};
var options = {
from: 3,
to: 30
};
var addresses = [address];
var history = new AddressHistory({
node: node,
options: options,
addresses: addresses
});
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
sinon.stub(history, 'getDetailedInfo', function(txid, next) {
this.detailedArray.push(txid);
next();
});
history._paginateWithDetails(txids, function(err, result) {
result.totalCount.should.equal(11);
result.items.should.deep.equal([7, 6, 5, 4, 3, 2, 1, 0]);
});
});
it('slice txids based on "from" and "to" (0 to 3)', function() {
var node = {};
var options = {
from: 0,
to: 3
};
var addresses = [address];
var history = new AddressHistory({
node: node,
options: options,
addresses: addresses
});
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
sinon.stub(history, 'getDetailedInfo', function(txid, next) {
this.detailedArray.push(txid);
next();
});
history._paginateWithDetails(txids, function(err, result) {
result.totalCount.should.equal(11);
result.items.should.deep.equal([10, 9, 8]);
});
});
it('will given an error if the full details is too long', function() {
var node = {};
var options = {
from: 0,
to: 3
};
var addresses = [address];
var history = new AddressHistory({
node: node,
options: options,
addresses: addresses
});
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
sinon.stub(history, 'getDetailedInfo', function(txid, next) {
this.detailedArray.push(txid);
next();
});
history.maxHistoryQueryLength = 1;
history._paginateWithDetails(txids, function(err) {
should.exist(err);
err.message.match(/Maximum/);
});
});
it('will give full result without pagination options', function() {
var node = {};
var options = {};
var addresses = [address];
var history = new AddressHistory({
node: node,
options: options,
addresses: addresses
});
var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
sinon.stub(history, 'getDetailedInfo', function(txid, next) {
this.detailedArray.push(txid);
next();
});
history._paginateWithDetails(txids, function(err, result) {
result.totalCount.should.equal(11);
result.items.should.deep.equal([10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
});
});
});
describe('#_mergeAndSortTxids', function() {
it('will merge and sort multiple summaries', function() {
var summaries = [
{
totalReceived: 10000000,
totalSpent: 0,
balance: 10000000,
appearances: 2,
unconfirmedBalance: 20000000,
unconfirmedAppearances: 2,
appearanceIds: {
'56fafeb01961831b926558d040c246b97709fd700adcaa916541270583e8e579': 154,
'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce': 120
},
unconfirmedAppearanceIds: {
'ec94d845c603f292a93b7c829811ac624b76e52b351617ca5a758e9d61a11681': 1452898347406,
'ed11a08e3102f9610bda44c80c46781d97936a4290691d87244b1b345b39a693': 1452898331964
}
},
{
totalReceived: 59990000,
totalSpent: 0,
balance: 49990000,
appearances: 3,
unconfirmedBalance: 1000000,
unconfirmedAppearances: 3,
appearanceIds: {
'bc992ad772eb02864db07ef248d31fb3c6826d25f1153ebf8c79df9b7f70fcf2': 156,
'f3c1ba3ef86a0420d6102e40e2cfc8682632ab95d09d86a27f5d466b9fa9da47': 152,
'f637384e9f81f18767ea50e00bce58fc9848b6588a1130529eebba22a410155f': 151
},
unconfirmedAppearanceIds: {
'f71bccef3a8f5609c7f016154922adbfe0194a96fb17a798c24077c18d0a9345': 1452897902377,
'edc080f2084eed362aa488ccc873a24c378dc0979aa29b05767517b70569414a': 1452897971363,
'f35e7e2a2334e845946f3eaca76890d9a68f4393ccc9fe37a0c2fb035f66d2e9': 1452897923107
}
}
];
var node = {};
var options = {};
var addresses = [address];
var history = new AddressHistory({
node: node,
options: options,
addresses: addresses
});
var txids = history._mergeAndSortTxids(summaries);
txids.should.deep.equal([
'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce',
'f637384e9f81f18767ea50e00bce58fc9848b6588a1130529eebba22a410155f',
'f3c1ba3ef86a0420d6102e40e2cfc8682632ab95d09d86a27f5d466b9fa9da47',
'56fafeb01961831b926558d040c246b97709fd700adcaa916541270583e8e579',
'bc992ad772eb02864db07ef248d31fb3c6826d25f1153ebf8c79df9b7f70fcf2',
'f71bccef3a8f5609c7f016154922adbfe0194a96fb17a798c24077c18d0a9345',
'f35e7e2a2334e845946f3eaca76890d9a68f4393ccc9fe37a0c2fb035f66d2e9',
'edc080f2084eed362aa488ccc873a24c378dc0979aa29b05767517b70569414a',
'ed11a08e3102f9610bda44c80c46781d97936a4290691d87244b1b345b39a693',
'ec94d845c603f292a93b7c829811ac624b76e52b351617ca5a758e9d61a11681'
]);
});
});
describe('#getDetailedInfo', function() {
it('will add additional information to existing this.transactions', function(done) {
var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
var tx = {
populateInputs: sinon.stub().callsArg(2),
__height: 20,
__timestamp: 1453134151,
isCoinbase: sinon.stub().returns(false),
getFee: sinon.stub().returns(1000)
};
var history = new AddressHistory({
node: {
services: {
db: {
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, null, tx),
tip: {
__height: 300
}
}
}
},
options: {},
addresses: []
});
history.getAddressDetailsForTransaction = sinon.stub().returns({
addresses: {},
satoshis: 1000,
});
history.getDetailedInfo(txid, function(err) {
if (err) {
throw err;
}
history.node.services.db.getTransactionWithBlockInfo.callCount.should.equal(1);
done();
});
});
it('will handle error from getTransactionFromBlock', function(done) {
var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
var history = new AddressHistory({
node: {
services: {
db: {
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, new Error('test')),
}
}
},
options: {},
addresses: []
});
history.getDetailedInfo(txid, function(err) {
err.message.should.equal('test');
done();
});
});
it('will handle error from populateInputs', function(done) {
var txid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
var history = new AddressHistory({
node: {
services: {
db: {
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, null, {
populateInputs: sinon.stub().callsArgWith(2, new Error('test'))
}),
}
}
},
options: {},
addresses: []
});
history.getDetailedInfo(txid, function(err) {
err.message.should.equal('test');
done();
});
});
it('will set this.transactions with correct information', function(done) {
// block #314159
// txid 30169e8bf78bc27c4014a7aba3862c60e2e3cce19e52f1909c8255e4b7b3174e
// outputIndex 1
var txAddress = '1Cj4UZWnGWAJH1CweTMgPLQMn26WRMfXmo';
var txString = '0100000001a08ee59fcd5d86fa170abb6d925d62d5c5c476359681b70877c04f270c4ef246000000008a47304402203fb9b476bb0c37c9b9ed5784ebd67ae589492be11d4ae1612be29887e3e4ce750220741ef83781d1b3a5df8c66fa1957ad0398c733005310d7d9b1d8c2310ef4f74c0141046516ad02713e51ecf23ac9378f1069f9ae98e7de2f2edbf46b7836096e5dce95a05455cc87eaa1db64f39b0c63c0a23a3b8df1453dbd1c8317f967c65223cdf8ffffffff02b0a75fac000000001976a91484b45b9bf3add8f7a0f3daad305fdaf6b73441ea88ac20badc02000000001976a914809dc14496f99b6deb722cf46d89d22f4beb8efd88ac00000000';
var previousTxString = '010000000155532fad2869bb951b0bd646a546887f6ee668c4c0ee13bf3f1c4bce6d6e3ed9000000008c4930460221008540795f4ef79b1d2549c400c61155ca5abbf3089c84ad280e1ba6db2a31abce022100d7d162175483d51174d40bba722e721542c924202a0c2970b07e680b51f3a0670141046516ad02713e51ecf23ac9378f1069f9ae98e7de2f2edbf46b7836096e5dce95a05455cc87eaa1db64f39b0c63c0a23a3b8df1453dbd1c8317f967c65223cdf8ffffffff02f0af3caf000000001976a91484b45b9bf3add8f7a0f3daad305fdaf6b73441ea88ac80969800000000001976a91421277e65777760d1f3c7c982ba14ed8f934f005888ac00000000';
var transaction = new Transaction();
var previousTransaction = new Transaction();
previousTransaction.fromString(previousTxString);
var previousTransactionTxid = '46f24e0c274fc07708b781963576c4c5d5625d926dbb0a17fa865dcd9fe58ea0';
transaction.fromString(txString);
var txid = transaction.hash;
transaction.__blockHash = '00000000000000001bb82a7f5973618cfd3185ba1ded04dd852a653f92a27c45';
transaction.__height = 314159;
transaction.__timestamp = 1407292005;
var history = new AddressHistory({
node: {
services: {
db: {
tip: {
__height: 314159
},
getTransactionWithBlockInfo: sinon.stub().callsArgWith(2, null, transaction),
getTransaction: function(prevTxid, queryMempool, callback) {
prevTxid.should.equal(previousTransactionTxid);
setImmediate(function() {
callback(null, previousTransaction);
});
}
}
}
},
options: {},
addresses: [txAddress]
});
var transactionInfo = {
addresses: {},
txid: txid,
timestamp: 1407292005,
satoshis: 48020000,
address: txAddress
};
transactionInfo.addresses[txAddress] = {};
transactionInfo.addresses[txAddress].outputIndexes = [1];
transactionInfo.addresses[txAddress].inputIndexes = [];
history.getDetailedInfo(txid, function(err) {
if (err) {
throw err;
}
var info = history.detailedArray[0];
info.addresses[txAddress].should.deep.equal({
outputIndexes: [1],
inputIndexes: []
});
info.satoshis.should.equal(48020000);
info.height.should.equal(314159);
info.confirmations.should.equal(1);
info.timestamp.should.equal(1407292005);
info.fees.should.equal(20000);
info.tx.should.equal(transaction);
done();
});
});
});
describe('#getAddressDetailsForTransaction', function() {
it('will calculate details for the transaction', function(done) {
/* jshint sub:true */
var tx = bitcore.Transaction({
'hash': 'b12b3ae8489c5a566b629a3c62ce4c51c3870af550fb5dc77d715b669a91343c',
'version': 1,
'inputs': [
{
'prevTxId': 'a2b7ea824a92f4a4944686e67ec1001bc8785348b8c111c226f782084077b543',
'outputIndex': 0,
'sequenceNumber': 4294967295,
'script': '47304402201b81c933297241960a57ae1b2952863b965ac8c9ec7466ff0b715712d27548d50220576e115b63864f003889443525f47c7cf0bc1e2b5108398da085b221f267ba2301210229766f1afa25ca499a51f8e01c292b0255a21a41bb6685564a1607a811ffe924',
'scriptString': '71 0x304402201b81c933297241960a57ae1b2952863b965ac8c9ec7466ff0b715712d27548d50220576e115b63864f003889443525f47c7cf0bc1e2b5108398da085b221f267ba2301 33 0x0229766f1afa25ca499a51f8e01c292b0255a21a41bb6685564a1607a811ffe924',
'output': {
'satoshis': 1000000000,
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
}
}
],
'outputs': [
{
'satoshis': 100000000,
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
},
{
'satoshis': 200000000,
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
},
{
'satoshis': 50000000,
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
},
{
'satoshis': 300000000,
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
},
{
'satoshis': 349990000,
'script': '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac'
}
],
'nLockTime': 0
});
var history = new AddressHistory({
node: {
network: bitcore.Networks.testnet
},
options: {},
addresses: ['mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW']
});
var details = history.getAddressDetailsForTransaction(tx);
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('#getConfirmationsDetail', function() {
it('the correct confirmations when included in the tip', function(done) {
var history = new AddressHistory({
node: {
services: {
db: {
tip: {
__height: 100
}
}
}
},
options: {},
addresses: []
});
var transaction = {
__height: 100
};
history.getConfirmationsDetail(transaction).should.equal(1);
done();
});
});
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,44 @@ var should = require('chai').should();
var sinon = require('sinon'); var sinon = require('sinon');
var bitcoinlib = require('../'); var bitcoinlib = require('../');
var Transaction = bitcoinlib.Transaction; var Transaction = bitcoinlib.Transaction;
var levelup = require('levelup');
describe('Bitcoin Transaction', function() { describe('Bitcoin Transaction', function() {
describe('#populateSpentInfo', function() {
it('will call db.getSpentInfo with correct arguments', function(done) {
var tx = new Transaction();
tx.to('1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i', 1000);
tx.to('3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou', 2000);
var expectedHash = tx.hash;
var expectedIndex = 2;
var expectedHeight = 300000;
var db = {
getSpentInfo: sinon.stub().callsArgWith(1, null, {
txid: expectedHash,
index: expectedIndex,
height: expectedHeight
})
};
tx.populateSpentInfo(db, {}, function(err) {
if (err) {
return done(err);
}
db.getSpentInfo.args[0][0].txid.should.equal(tx.hash);
db.getSpentInfo.args[0][0].index.should.equal(0);
tx.outputs[0].__spentTxId.should.equal(expectedHash);
tx.outputs[0].__spentIndex.should.equal(expectedIndex);
tx.outputs[0].__spentHeight.should.equal(expectedHeight);
db.getSpentInfo.args[1][0].txid.should.equal(tx.hash);
db.getSpentInfo.args[1][0].index.should.equal(1);
tx.outputs[1].__spentTxId.should.equal(expectedHash);
tx.outputs[1].__spentIndex.should.equal(expectedIndex);
tx.outputs[1].__spentHeight.should.equal(expectedHeight);
done();
});
});
});
describe('#populateInputs', function() { describe('#populateInputs', function() {
it('will call _populateInput with transactions', function() { it('will call _populateInput with transactions', function() {
var tx = new Transaction(); var tx = new Transaction();
@ -22,6 +57,17 @@ describe('Bitcoin Transaction', function() {
tx._populateInput.args[0][2].should.equal(transactions); tx._populateInput.args[0][2].should.equal(transactions);
}); });
}); });
it('will skip coinbase transactions', function() {
var tx = new Transaction();
tx.isCoinbase = sinon.stub().returns(true);
tx._populateInput = sinon.stub().callsArg(3);
tx.inputs = ['input'];
var transactions = [];
var db = {};
tx.populateInputs(db, transactions, function(err) {
tx._populateInput.callCount.should.equal(0);
});
});
}); });
describe('#_populateInput', function() { describe('#_populateInput', function() {
@ -29,6 +75,15 @@ describe('Bitcoin Transaction', function() {
prevTxId: new Buffer('d6cffbb343a6a41eeaa199478c985493843bfe6a59d674a5c188787416cbcda3', 'hex'), prevTxId: new Buffer('d6cffbb343a6a41eeaa199478c985493843bfe6a59d674a5c188787416cbcda3', 'hex'),
outputIndex: 0 outputIndex: 0
}; };
it('should give an error if the input does not have a prevTxId', function(done) {
var badInput = {};
var tx = new Transaction();
tx._populateInput({}, badInput, [], function(err) {
should.exist(err);
err.message.should.equal('Input is expected to have prevTxId as a buffer');
done();
});
});
it('should give an error if the input does not have a valid prevTxId', function(done) { it('should give an error if the input does not have a valid prevTxId', function(done) {
var badInput = { var badInput = {
prevTxId: 'bad' prevTxId: 'bad'
@ -43,7 +98,7 @@ describe('Bitcoin Transaction', function() {
it('if an error happened it should pass it along', function(done) { it('if an error happened it should pass it along', function(done) {
var tx = new Transaction(); var tx = new Transaction();
var db = { var db = {
getTransaction: sinon.stub().callsArgWith(2, new Error('error')) getTransaction: sinon.stub().callsArgWith(1, new Error('error'))
}; };
tx._populateInput(db, input, [], function(err) { tx._populateInput(db, input, [], function(err) {
should.exist(err); should.exist(err);
@ -54,7 +109,7 @@ describe('Bitcoin Transaction', function() {
it('should return an error if the transaction for the input does not exist', function(done) { it('should return an error if the transaction for the input does not exist', function(done) {
var tx = new Transaction(); var tx = new Transaction();
var db = { var db = {
getTransaction: sinon.stub().callsArgWith(2, new levelup.errors.NotFoundError()) getTransaction: sinon.stub().callsArgWith(1, null, null)
}; };
tx._populateInput(db, input, [], function(err) { tx._populateInput(db, input, [], function(err) {
should.exist(err); should.exist(err);
@ -65,7 +120,7 @@ describe('Bitcoin Transaction', function() {
it('should look through poolTransactions if database does not have transaction', function(done) { it('should look through poolTransactions if database does not have transaction', function(done) {
var tx = new Transaction(); var tx = new Transaction();
var db = { var db = {
getTransaction: sinon.stub().callsArgWith(2, new levelup.errors.NotFoundError()) getTransaction: sinon.stub().callsArgWith(1, null, null)
}; };
var transactions = [ var transactions = [
{ {
@ -79,12 +134,12 @@ describe('Bitcoin Transaction', function() {
done(); done();
}); });
}); });
it('should not return an error if an error did not occur', function(done) { it('should set the output on the input', function(done) {
var prevTx = new Transaction(); var prevTx = new Transaction();
prevTx.outputs = ['output']; prevTx.outputs = ['output'];
var tx = new Transaction(); var tx = new Transaction();
var db = { var db = {
getTransaction: sinon.stub().callsArgWith(2, null, prevTx) getTransaction: sinon.stub().callsArgWith(1, null, prevTx)
}; };
tx._populateInput(db, input, [], function(err) { tx._populateInput(db, input, [], function(err) {
should.not.exist(err); should.not.exist(err);
@ -94,27 +149,4 @@ describe('Bitcoin Transaction', function() {
}); });
}); });
describe('#_checkSpent', function() {
it('should return an error if input was spent', function(done) {
var tx = new Transaction();
var db = {
isSpentDB: sinon.stub().callsArgWith(1, true)
};
tx._checkSpent(db, [], 'input', function(err) {
should.exist(err);
err.message.should.equal('Input already spent');
done();
});
});
it('should not return an error if input was unspent', function(done) {
var tx = new Transaction();
var db = {
isSpentDB: sinon.stub().callsArgWith(1, false)
};
tx._checkSpent(db, [], 'input', function(err) {
should.not.exist(err);
done();
});
});
});
}); });