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"
- "v4"
script:
- _mocha -R spec regtest/p2p.js
- _mocha -R spec regtest/bitcoind.js
- _mocha -R spec regtest/cluster.js
- _mocha -R spec regtest/node.js
- _mocha -R spec --recursive
- npm run regtest
- npm run test

View File

@ -3,23 +3,10 @@
var createError = require('errno').create;
var BitcoreNodeError = createError('BitcoreNodeError');
var NoOutputs = createError('NoOutputs', BitcoreNodeError);
var NoOutput = createError('NoOutput', BitcoreNodeError);
var Wallet = createError('WalletError', 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);
var RPCError = createError('RPCError', BitcoreNodeError);
module.exports = {
Error: BitcoreNodeError,
NoOutputs: NoOutputs,
NoOutput: NoOutput,
Wallet: Wallet,
Consensus: Consensus,
Transaction: Transaction
RPCError: RPCError
};

View File

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

View File

@ -14,7 +14,9 @@ var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var index = require('../');
var errors = index.errors;
var log = index.log;
var utils = require('../utils');
var Service = require('../service');
var Transaction = require('../transaction');
@ -45,6 +47,12 @@ function Bitcoin(options) {
this.subscriptions.transaction = [];
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
this._initClients();
}
@ -52,7 +60,21 @@ util.inherits(Bitcoin, Service);
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() {
// caches valid until there is a new block
@ -70,8 +92,8 @@ Bitcoin.prototype._initCaches = function() {
this.blockHeaderCache = LRU(288);
this.zmqKnownTransactions = LRU(50);
this.zmqKnownBlocks = LRU(50);
this.zmqLastBlock = 0;
this.zmqUpdateTipTimeout = false;
this.lastTip = 0;
this.lastTipTimeout = false;
};
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) {
/* jshint maxstatements: 25 */
@ -169,6 +200,11 @@ Bitcoin.prototype._loadSpawnConfiguration = function(node) {
mkdirp.sync(spawnOptions.datadir);
}
if (!fs.existsSync(configPath)) {
var defaultConfig = this._getDefaultConfig();
fs.writeFileSync(configPath, defaultConfig);
}
var file = fs.readFileSync(configPath);
var unparsed = file.toString().split('\n');
for(var i = 0; i < unparsed.length; i++) {
@ -187,6 +223,11 @@ Bitcoin.prototype._loadSpawnConfiguration = function(node) {
var spawnConfig = this.spawn.config;
this._checkConfigIndexes(spawnConfig, node);
};
Bitcoin.prototype._checkConfigIndexes = function(spawnConfig, node) {
$.checkState(
spawnConfig.txindex && spawnConfig.txindex === 1,
'"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.');
node._reindex = true;
}
};
Bitcoin.prototype._resetCaches = function() {
@ -245,11 +285,11 @@ Bitcoin.prototype._resetCaches = function() {
};
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) {
var err = new Error(errObj.message);
var err = new errors.RPCError(errObj.message);
err.code = errObj.code;
return err;
};
@ -274,11 +314,11 @@ Bitcoin.prototype._initChain = function(callback) {
return callback(self._wrapRPCError(err));
}
var blockhash = response.result;
self.getBlock(blockhash, function(err, block) {
self.getRawBlock(blockhash, function(err, blockBuffer) {
if (err) {
return callback(err);
}
self.genesisBuffer = block.toBuffer();
self.genesisBuffer = blockBuffer;
self.emit('ready');
log.info('Bitcoin Daemon Ready');
callback();
@ -303,55 +343,72 @@ Bitcoin.prototype._getNetworkOption = function() {
Bitcoin.prototype._zmqBlockHandler = function(node, message) {
var self = this;
function updateChain() {
var hex = message.toString('hex');
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);
}
// Update the current chain tip
self._rapidProtectedUpdateTip(node, message);
// Notify block subscribers
var id = message.toString('binary');
if (!self.zmqKnownBlocks[id]) {
self.zmqKnownBlocks[id] = true;
if (!self.zmqKnownBlocks.get(id)) {
self.zmqKnownBlocks.set(id, true);
self.emit('block', message);
for (var i = 0; i < this.subscriptions.block.length; i++) {
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) {
var self = this;
var id = message.toString('binary');
if (!self.zmqKnownTransactions[id]) {
self.zmqKnownTransactions[id] = true;
if (!self.zmqKnownTransactions.get(id)) {
self.zmqKnownTransactions.set(id, true);
self.emit('tx', message);
}
// Notify transaction subscribers
for (var i = 0; i < this.subscriptions.transaction.length; i++) {
this.subscriptions.transaction[i].emit('bitcoind/transaction', message);
// Notify transaction subscribers
for (var i = 0; i < this.subscriptions.transaction.length; i++) {
this.subscriptions.transaction[i].emit('bitcoind/transaction', message.toString('hex'));
}
}
};
Bitcoin.prototype._subscribeZmqEvents = function(node) {
@ -414,21 +470,24 @@ Bitcoin.prototype._initZmqSubSocket = function(node, zmqUrl) {
Bitcoin.prototype._checkReindex = function(node, callback) {
var self = this;
var interval;
function finish(err) {
clearInterval(interval);
callback(err);
}
if (node._reindex) {
var interval = setInterval(function() {
interval = setInterval(function() {
node.client.syncPercentage(function(err, percentSynced) {
if (err) {
return log.error(self._wrapRPCError(err));
return finish(self._wrapRPCError(err));
}
log.info('Bitcoin Core Daemon Reindex Percentage: ' + percentSynced.toFixed(2));
if (Math.round(percentSynced) >= 100) {
node._reindex = false;
callback();
clearInterval(interval);
finish();
}
});
}, self._reindexWait);
} else {
callback();
}
@ -787,11 +846,15 @@ Bitcoin.prototype.getAddressTxids = function(addressArg, options, callback) {
};
Bitcoin.prototype._getConfirmationsDetail = function(transaction) {
$.checkState(this.height > 0, 'current height is unknown');
var confirmations = 0;
if (transaction.__height >= 0) {
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) {
@ -904,6 +967,7 @@ Bitcoin.prototype._getAddressStrings = function(addresses) {
Bitcoin.prototype._paginateTxids = function(fullTxids, from, to) {
var txids;
if (from >= 0 && to >= 0) {
$.checkState(from < to, '"from" is expected to be less than "to"');
txids = fullTxids.slice(from, to);
} else {
txids = fullTxids;
@ -933,7 +997,11 @@ Bitcoin.prototype.getAddressHistory = function(addressArg, options, callback) {
}
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(
txids,
@ -1304,6 +1372,7 @@ Bitcoin.prototype.getTransaction = function(txid, callback) {
* @param {Function} callback
*/
Bitcoin.prototype.getTransactionWithBlockInfo = function(txid, callback) {
// TODO give response back as standard js object with bitcore tx
var self = this;
var tx = self.transactionInfoCache.get(txid);
if (tx) {
@ -1408,9 +1477,11 @@ Bitcoin.prototype.generateBlock = function(num, callback) {
*/
Bitcoin.prototype.stop = function(callback) {
if (this.spawn && this.spawn.process) {
this.spawn.process.once('exit', function(err, status) {
if (err) {
return callback(err);
this.spawn.process.once('exit', function(code) {
if (code !== 0) {
var error = new Error('bitcoind spawned process exited with status code: ' + code);
error.code = code;
return callback(error);
} else {
return callback();
}

View File

@ -4,9 +4,6 @@ var async = require('async');
var bitcore = require('bitcore-lib');
var Transaction = bitcore.Transaction;
var index = require('./');
var errors = index.errors;
var MAX_TRANSACTION_LIMIT = 5;
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) {
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');
db.getTransaction(txid, function(err, prevTx) {
if(!prevTx) {
if(err) {
return callback(err);
} else if (!prevTx) {
// Check the pool for transaction
for(var i = 0; i < poolTransactions.length; i++) {
if(txid === poolTransactions[i].hash) {
@ -65,25 +64,10 @@ Transaction.prototype._populateInput = function(db, input, poolTransactions, cal
return callback();
}
}
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": {
"install": "./scripts/install",
"test": "NODE_ENV=test mocha -R spec --recursive",
"regtest": "./scripts/regtest",
"coverage": "NODE_ENV=test istanbul cover _mocha -- --recursive"
},
"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
#irc=0
#upnp=0
upnp=0
server=1
whitelist=127.0.0.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
rpcport=50001
rpcallowip=127.0.0.1
rpcuser=bitcoin
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';
var should = require('chai').should();
var path = require('path');
var defaultBaseConfig = require('../../lib/scaffold/default-base-config');
describe('#defaultBaseConfig', function() {
@ -9,29 +10,19 @@ describe('#defaultBaseConfig', function() {
var home = process.env.HOME;
var info = defaultBaseConfig();
info.path.should.equal(cwd);
info.config.datadir.should.equal(home + '/.bitcoin');
info.config.network.should.equal('livenet');
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() {
var cwd = process.cwd();
var home = process.env.HOME;
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.port.should.equal(3001);
info.config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']);
});
it('be able to specify a datadir', function() {
var cwd = process.cwd();
var home = process.env.HOME;
var info = defaultBaseConfig({datadir: './data2', network: 'testnet'});
info.path.should.equal(cwd);
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']);
info.config.servicesConfig.bitcoind.spawn.datadir.should.equal('./data2');
});
});

View File

@ -1,21 +1,29 @@
'use strict';
var path = require('path');
var should = require('chai').should();
var sinon = require('sinon');
var proxyquire = require('proxyquire');
describe('#defaultConfig', function() {
var expectedExecPath = path.resolve(__dirname, '../../bin/bitcoind');
it('will return expected configuration', function() {
var config = JSON.stringify({
datadir: process.env.HOME + '/.bitcore/data',
network: 'livenet',
port: 3001,
services: [
'bitcoind',
'db',
'address',
'web'
]
],
servicesConfig: {
bitcoind: {
spawn: {
datadir: process.env.HOME + '/.bitcore/data',
exec: expectedExecPath
}
}
}
}, null, 2);
var defaultConfig = proxyquire('../../lib/scaffold/default-config', {
fs: {
@ -32,28 +40,35 @@ describe('#defaultConfig', function() {
sync: sinon.stub()
}
});
var cwd = process.cwd();
var home = process.env.HOME;
var info = defaultConfig();
info.path.should.equal(home + '/.bitcore');
info.config.datadir.should.equal(home + '/.bitcore/data');
info.config.network.should.equal('livenet');
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() {
var config = JSON.stringify({
datadir: process.env.HOME + '/.bitcore/data',
network: 'livenet',
port: 3001,
services: [
'bitcoind',
'db',
'address',
'web',
'insight-api',
'insight-ui'
]
],
servicesConfig: {
bitcoind: {
spawn: {
datadir: process.env.HOME + '/.bitcore/data',
exec: expectedExecPath
}
}
}
}, null, 2);
var defaultConfig = proxyquire('../../lib/scaffold/default-config', {
fs: {
@ -75,16 +90,17 @@ describe('#defaultConfig', function() {
additionalServices: ['insight-api', 'insight-ui']
});
info.path.should.equal(home + '/.bitcore');
info.config.datadir.should.equal(home + '/.bitcore/data');
info.config.network.should.equal('livenet');
info.config.port.should.equal(3001);
info.config.services.should.deep.equal([
'bitcoind',
'db',
'address',
'web',
'insight-api',
'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 sinon = require('sinon');
var proxyquire = require('proxyquire');
var AddressService = require('../../lib/services/address');
var BitcoinService = require('../../lib/services/bitcoind');
describe('#start', function() {
@ -13,8 +13,8 @@ describe('#start', function() {
var node;
var TestNode = function(options) {
options.services[0].should.deep.equal({
name: 'address',
module: AddressService,
name: 'bitcoind',
module: BitcoinService,
config: {}
});
};
@ -32,7 +32,7 @@ describe('#start', function() {
path: __dirname,
config: {
services: [
'address'
'bitcoind'
],
datadir: './data'
}
@ -67,8 +67,8 @@ describe('#start', function() {
var node;
var TestNode = function(options) {
options.services[0].should.deep.equal({
name: 'address',
module: AddressService,
name: 'bitcoind',
module: BitcoinService,
config: {
param: 'test'
}
@ -88,10 +88,10 @@ describe('#start', function() {
path: __dirname,
config: {
services: [
'address'
'bitcoind'
],
servicesConfig: {
'address': {
'bitcoind': {
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() {
var stub;
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 bitcoinlib = require('../');
var Transaction = bitcoinlib.Transaction;
var levelup = require('levelup');
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() {
it('will call _populateInput with transactions', function() {
var tx = new Transaction();
@ -22,6 +57,17 @@ describe('Bitcoin Transaction', function() {
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() {
@ -29,6 +75,15 @@ describe('Bitcoin Transaction', function() {
prevTxId: new Buffer('d6cffbb343a6a41eeaa199478c985493843bfe6a59d674a5c188787416cbcda3', 'hex'),
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) {
var badInput = {
prevTxId: 'bad'
@ -43,7 +98,7 @@ describe('Bitcoin Transaction', function() {
it('if an error happened it should pass it along', function(done) {
var tx = new Transaction();
var db = {
getTransaction: sinon.stub().callsArgWith(2, new Error('error'))
getTransaction: sinon.stub().callsArgWith(1, new Error('error'))
};
tx._populateInput(db, input, [], function(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) {
var tx = new Transaction();
var db = {
getTransaction: sinon.stub().callsArgWith(2, new levelup.errors.NotFoundError())
getTransaction: sinon.stub().callsArgWith(1, null, null)
};
tx._populateInput(db, input, [], function(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) {
var tx = new Transaction();
var db = {
getTransaction: sinon.stub().callsArgWith(2, new levelup.errors.NotFoundError())
getTransaction: sinon.stub().callsArgWith(1, null, null)
};
var transactions = [
{
@ -79,12 +134,12 @@ describe('Bitcoin Transaction', function() {
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();
prevTx.outputs = ['output'];
var tx = new Transaction();
var db = {
getTransaction: sinon.stub().callsArgWith(2, null, prevTx)
getTransaction: sinon.stub().callsArgWith(1, null, prevTx)
};
tx._populateInput(db, input, [], function(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();
});
});
});
});