Merge pull request #170 from braydonf/coverage
Improve test coverage and cleanup.
This commit is contained in:
commit
fd2790daab
|
@ -1,31 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var BitcoinNode = require('..').Node;
|
||||
var chainlib = require('chainlib');
|
||||
var log = chainlib.log;
|
||||
log.debug = function() {};
|
||||
|
||||
var configuration = {
|
||||
datadir: process.env.BITCORENODE_DIR || '~/.bitcoin',
|
||||
network: 'testnet'
|
||||
};
|
||||
|
||||
var node = new BitcoinNode(configuration);
|
||||
|
||||
var count = 0;
|
||||
var interval;
|
||||
|
||||
node.on('ready', function() {
|
||||
interval = setInterval(function() {
|
||||
log.info('Sync Status: Tip:', node.chain.tip.hash, 'Height:', node.chain.tip.__height, 'Rate:', count/10, 'blocks per second');
|
||||
count = 0;
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
node.on('error', function(err) {
|
||||
log.error(err);
|
||||
});
|
||||
|
||||
node.chain.on('addblock', function(block) {
|
||||
count++;
|
||||
});
|
File diff suppressed because one or more lines are too long
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"unspent": [
|
||||
{
|
||||
"txid": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
|
||||
"outputIndex": 0
|
||||
},
|
||||
{
|
||||
"txid": "8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb",
|
||||
"outputIndex": 1
|
||||
},
|
||||
{
|
||||
"txid": "226bbc4b1f851857f37aa96e9eb702946fc128b055e4decc684740005f5044cf",
|
||||
"outputIndex": 0
|
||||
}
|
||||
],
|
||||
"spent": [
|
||||
{
|
||||
"txid": "0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9",
|
||||
"outputIndex": 0
|
||||
},
|
||||
{
|
||||
"txid": "fff2525b8931402dd09222c50775608f75787bd2b87e56995a7bdd30f79702c4",
|
||||
"outputIndex": 1
|
||||
}
|
||||
]
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -1,188 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
// These tests require a fully synced Bitcore Code data directory.
|
||||
// To run the tests: $ mocha -R spec livenet.js
|
||||
|
||||
var chai = require('chai');
|
||||
var bitcore = require('bitcore');
|
||||
var bitcoind;
|
||||
|
||||
/* jshint unused: false */
|
||||
var should = chai.should();
|
||||
var assert = chai.assert;
|
||||
var sinon = require('sinon');
|
||||
var txData = require('./livenet-tx-data.json');
|
||||
var blockData = require('./livenet-block-data.json');
|
||||
var testTxData = require('./livenet-tx-data.json');
|
||||
var spentData = require('./livenet-spents.json').spent;
|
||||
var unspentData = require('./livenet-spents.json').unspent;
|
||||
var testBlockData = require('./testnet-block-data.json');
|
||||
|
||||
describe('Basic Functionality', function() {
|
||||
|
||||
before(function(done) {
|
||||
this.timeout(30000);
|
||||
bitcoind = require('../').daemon({
|
||||
datadir: process.env.BITCOINDJS_DIR || '~/.bitcoin',
|
||||
});
|
||||
|
||||
bitcoind.on('error', function(err) {
|
||||
bitcoind.log('error="%s"', err.message);
|
||||
});
|
||||
|
||||
bitcoind.on('open', function(status) {
|
||||
bitcoind.log('status="%s"', status);
|
||||
});
|
||||
|
||||
console.log('Waiting for Bitcoin Core to initialize...');
|
||||
|
||||
bitcoind.on('ready', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
this.timeout(20000);
|
||||
bitcoind.stop(function(err, result) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('get transactions by hash', function() {
|
||||
txData.forEach(function(data) {
|
||||
var tx = bitcore.Transaction();
|
||||
tx.fromString(data);
|
||||
it('for tx ' + tx.hash, function(done) {
|
||||
bitcoind.getTransaction(tx.hash, true, function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
assert(response.toString('hex') === data, 'incorrect tx data for ' + tx.hash);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('determine if outpoint is unspent/spent', function() {
|
||||
spentData.forEach(function(data) {
|
||||
it('for spent txid ' + data.txid + ' and output ' + data.outputIndex, function() {
|
||||
var spent = bitcoind.isSpent(data.txid, data.outputIndex, true);
|
||||
spent.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
unspentData.forEach(function(data) {
|
||||
it('for unspent txid ' + data.txid + ' and output ' + data.outputIndex, function() {
|
||||
var spent = bitcoind.isSpent(data.txid, data.outputIndex, true);
|
||||
spent.should.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get blocks by hash', function() {
|
||||
|
||||
blockData.forEach(function(data) {
|
||||
var block = bitcore.Block.fromString(data);
|
||||
it('block ' + block.hash, function(done) {
|
||||
bitcoind.getBlock(block.hash, function(err, response) {
|
||||
assert(response.toString('hex') === data, 'incorrect block data for ' + block.hash);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get blocks by height', function() {
|
||||
|
||||
var knownHeights = [
|
||||
[0, '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'],
|
||||
[1, '00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048'],
|
||||
[100000,'000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506'],
|
||||
[314159, '00000000000000001bb82a7f5973618cfd3185ba1ded04dd852a653f92a27c45']
|
||||
];
|
||||
|
||||
knownHeights.forEach(function(data) {
|
||||
it('block at height ' + data[0], function(done) {
|
||||
bitcoind.getBlock(data[0], function(err, response) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
var block = bitcore.Block.fromBuffer(response);
|
||||
block.hash.should.equal(data[1]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('get chain work', function() {
|
||||
it('will get the total work for the genesis block via hash', function() {
|
||||
var hash = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f';
|
||||
var work = bitcoind.getChainWork(hash);
|
||||
work.should.equal('0000000000000000000000000000000000000000000000000000000100010001');
|
||||
});
|
||||
it('will get the total work for block #300000 via hash', function() {
|
||||
var hash = '000000000000000082ccf8f1557c5d40b21edabb18d2d691cfbf87118bac7254';
|
||||
var work = bitcoind.getChainWork(hash);
|
||||
work.should.equal('000000000000000000000000000000000000000000005a7b3c42ea8b844374e9');
|
||||
});
|
||||
it('will return undefined for unknown block', function() {
|
||||
var hash = 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';
|
||||
var work = bitcoind.getChainWork(hash);
|
||||
should.equal(work, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mempool functionality', function() {
|
||||
|
||||
var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1';
|
||||
var utxo = {
|
||||
address: fromAddress,
|
||||
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
||||
outputIndex: 0,
|
||||
script: bitcore.Script.buildPublicKeyHashOut(fromAddress).toString(),
|
||||
satoshis: 100000
|
||||
};
|
||||
var toAddress = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc';
|
||||
var changeAddress = 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up';
|
||||
var changeAddressP2SH = '2N7T3TAetJrSCruQ39aNrJvYLhG1LJosujf';
|
||||
var privateKey = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY';
|
||||
var private1 = '6ce7e97e317d2af16c33db0b9270ec047a91bff3eff8558afb5014afb2bb5976';
|
||||
var private2 = 'c9b26b0f771a0d2dad88a44de90f05f416b3b385ff1d989343005546a0032890';
|
||||
var tx = new bitcore.Transaction();
|
||||
tx.from(utxo);
|
||||
tx.to(toAddress, 50000);
|
||||
tx.change(changeAddress);
|
||||
tx.sign(privateKey);
|
||||
|
||||
it('will add an unchecked transaction', function() {
|
||||
var added = bitcoind.addMempoolUncheckedTransaction(tx.serialize());
|
||||
added.should.equal(true);
|
||||
bitcoind.getTransaction(tx.hash, true, function(err, txBuffer) {
|
||||
if(err) {
|
||||
throw err;
|
||||
}
|
||||
var expected = tx.toBuffer().toString('hex');
|
||||
txBuffer.toString('hex').should.equal(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('get outputs by address', function() {
|
||||
var outputs = bitcoind.getMempoolOutputs(changeAddress);
|
||||
var expected = [
|
||||
{
|
||||
script: 'OP_DUP OP_HASH160 073b7eae2823efa349e3b9155b8a735526463a0f OP_EQUALVERIFY OP_CHECKSIG',
|
||||
satoshis: 40000,
|
||||
txid: tx.hash,
|
||||
outputIndex: 1
|
||||
}
|
||||
];
|
||||
outputs.should.deep.equal(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +0,0 @@
|
|||
[
|
||||
"0100000001eccd472c53ec7827eb8952a32d001ca95072ebe0a530f09cdf7219c0ce3f47be010000006a4730440220499cf8379c82301df2295c1581e1d683fec8cc201097efef748fcf3536532882022048c35b24485ec57f387745daf9fc50414ba76e50e914b53b9070f0a4a552f662012102a82fb2ce6e2ea8abb977d9825a2360c9447003dc91da5b8ffa5fc4b3c7808f3fffffffff020001b2c4000000001976a91442f0eb6de4628a6a7ae1afc85b027ff5e0aa25de88ac00437f1f690000001976a91443a3fc9d2ec090a08775957c364073e8b69cfe0688ac00000000",
|
||||
"01000000021ae3aa69180bbf3d898cd31232224cfe6a2f21ff34051b6fc14f99528e2f0aee010000006b483045022100c9e8656a407fc1fb379b1669abbed3415483bcd20929ebc7fa2f7ecf7c7a7f7b022021dca2336f14df90f201ff8caa8d7a4b8e1760d07afecef880e0cfcbd13756d70121038f4478ad50111c8d04c692fcb6ce560f617a1d01b3056436ebf906d802a422afffffffff1ae3aa69180bbf3d898cd31232224cfe6a2f21ff34051b6fc14f99528e2f0aee030000008a4730440220275f9ab3083c78b368f1e33341275c776fc4af4105ee2646004e9ffb4cba639302202739d0063726a7bcea6681172892c8ab8c1c0b09b1a4d5ed1dc0cf9bea7b18990141048dcb436c4c027e8a5826bdf3c8b6cb326abe66a2d34a18aa473442d8c535f902da45df071964fc8304658b20f9dd1ed241dc48578bf775624a9bd8c52b92c389ffffffff02402b9b00000000001976a914f4f065395c82c6f28d3c73cef113d6da0f90d82a88ac5423f984000000001976a9145af0fe772e1600100b2000f8279f78ce66b7e1d588ac00000000",
|
||||
"0100000002741453f89d091381329178b3d38cc213c21caede56f5a4a6b74e16ae9bd78b20000000006b483045022100bc0fa3e50ba16589664fd368139c02e3a9da55e20cceed340645b00f6466edac022068cad98f90006534afa2c38551248c6161861e32cb1af6ee1d0f75886263f9c20121038e9dac10e76c6c48a9b791de55f3ea009ee5d10532088f2eb9991d55b03b44e8ffffffff092910ede6e477e9589f8ce00807154378fcbd482d630b2c8cdd10243e74db79010000006b4830450221009cc13ef1acfdfc4535d888b1ea495b3c543e4fabdf38ce8cde4a37171136977c02201a0571e3ab3fd70ca4d97193fcd7999b35760ea4cac404edd1e50a338e50a28d012102e397fa30f882d076e393deec070d423138bd9544aa4cb53450b609713bb327aaffffffff02d5420400000000001976a914171e633a137d5c2275174380c6ca761d7e30365f88acb01a0200000000001976a914263ca88fd638fdb0e2ee8e2a16ec21ecea7a3d7e88ac00000000"
|
||||
]
|
|
@ -12,7 +12,7 @@ function getDefaultConfig() {
|
|||
config: {
|
||||
datadir: process.env.BITCORENODE_DIR || path.resolve(process.env.HOME, '.bitcoin'),
|
||||
network: process.env.BITCORENODE_NETWORK || 'livenet',
|
||||
port: process.env.BITCORENODE_PORT || 3001,
|
||||
port: Number(process.env.BITCORENODE_PORT) || 3001,
|
||||
services: ['bitcoind', 'db', 'address', 'web']
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var socketio = require('socket.io');
|
||||
var BitcoreNode = require('../node');
|
||||
var index = require('../');
|
||||
var bitcore = require('bitcore');
|
||||
|
@ -10,35 +9,43 @@ var $ = bitcore.util.preconditions;
|
|||
var log = index.log;
|
||||
log.debug = function() {};
|
||||
|
||||
var count = 0;
|
||||
var interval = false;
|
||||
|
||||
function start(options) {
|
||||
/* jshint maxstatements: 100 */
|
||||
|
||||
/**
|
||||
* This function will loop over the configuration for services and require the
|
||||
* specified modules, and assemble an array in this format:
|
||||
* [
|
||||
* {
|
||||
* name: 'bitcoind',
|
||||
* config: {},
|
||||
* module: BitcoinService
|
||||
* }
|
||||
* ]
|
||||
* @param {Function} req - The require function to use
|
||||
* @param {Object} config
|
||||
* @param {Array} config.services - An array of strings of service names.
|
||||
* @returns {Array}
|
||||
*/
|
||||
function setupServices(req, config) {
|
||||
var services = [];
|
||||
|
||||
var configPath = options.path;
|
||||
var config = options.config;
|
||||
|
||||
if (config.services) {
|
||||
for (var i = 0; i < config.services.length; i++) {
|
||||
var service = {};
|
||||
service.name = config.services[i];
|
||||
service.config = config.servicesConfig && config.servicesConfig[service.name] ? config.servicesConfig[service.name] : {};
|
||||
|
||||
var hasConfig = config.servicesConfig && config.servicesConfig[service.name];
|
||||
service.config = hasConfig ? config.servicesConfig[service.name] : {};
|
||||
|
||||
try {
|
||||
// first try in the built-in bitcore-node services directory
|
||||
service.module = require(path.resolve(__dirname, '../services/' + service.name));
|
||||
service.module = req(path.resolve(__dirname, '../services/' + service.name));
|
||||
} catch(e) {
|
||||
|
||||
// check if the package.json specifies a specific file to use
|
||||
var servicePackage = require(service.name + '/package.json');
|
||||
var servicePackage = req(service.name + '/package.json');
|
||||
var serviceModule = service.name;
|
||||
if (servicePackage.bitcoreNode) {
|
||||
serviceModule = service.name + '/' + servicePackage.bitcoreNode;
|
||||
}
|
||||
service.module = require(serviceModule);
|
||||
service.module = req(serviceModule);
|
||||
}
|
||||
|
||||
// check that the service supports expected methods
|
||||
|
@ -54,16 +61,18 @@ function start(options) {
|
|||
services.push(service);
|
||||
}
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
var fullConfig = _.clone(config);
|
||||
/**
|
||||
* Will register event handlers to log the current db sync status.
|
||||
* @param {Node} node
|
||||
*/
|
||||
function registerSyncHandlers(node, delay) {
|
||||
|
||||
// expand to the full path
|
||||
fullConfig.datadir = path.resolve(configPath, config.datadir);
|
||||
|
||||
// load the services
|
||||
fullConfig.services = services;
|
||||
|
||||
var node = new BitcoreNode(fullConfig);
|
||||
delay = delay || 10000;
|
||||
var interval = false;
|
||||
var count = 0;
|
||||
|
||||
function logSyncStatus() {
|
||||
log.info(
|
||||
|
@ -74,18 +83,7 @@ function start(options) {
|
|||
}
|
||||
|
||||
node.on('synced', function() {
|
||||
// Stop logging of sync status
|
||||
clearInterval(interval);
|
||||
interval = false;
|
||||
logSyncStatus();
|
||||
});
|
||||
|
||||
node.on('ready', function() {
|
||||
log.info('Bitcore Node ready');
|
||||
});
|
||||
|
||||
node.on('error', function(err) {
|
||||
log.error(err);
|
||||
});
|
||||
|
||||
node.on('ready', function() {
|
||||
|
@ -96,7 +94,7 @@ function start(options) {
|
|||
interval = setInterval(function() {
|
||||
logSyncStatus();
|
||||
count = 0;
|
||||
}, 10000);
|
||||
}, delay);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -104,41 +102,88 @@ function start(options) {
|
|||
node.on('stopping', function() {
|
||||
clearInterval(interval);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Will register event handlers to stop the node for `process` events
|
||||
* `uncaughtException` and `SIGINT`.
|
||||
* @param {Object} proc - The Node.js process
|
||||
* @param {Node} node
|
||||
*/
|
||||
function registerExitHandlers(proc, node) {
|
||||
|
||||
function exitHandler(options, err) {
|
||||
if (err) {
|
||||
log.error('uncaught exception:', err);
|
||||
if(err.stack) {
|
||||
console.log(err.stack);
|
||||
log.error(err.stack);
|
||||
}
|
||||
node.stop(function(err) {
|
||||
if(err) {
|
||||
log.error('Failed to stop services: ' + err);
|
||||
}
|
||||
process.exit(-1);
|
||||
proc.exit(-1);
|
||||
});
|
||||
}
|
||||
if (options.sigint) {
|
||||
node.stop(function(err) {
|
||||
if(err) {
|
||||
log.error('Failed to stop services: ' + err);
|
||||
return process.exit(1);
|
||||
return proc.exit(1);
|
||||
}
|
||||
|
||||
log.info('Halted');
|
||||
process.exit(0);
|
||||
proc.exit(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//catches uncaught exceptions
|
||||
process.on('uncaughtException', exitHandler.bind(null, {exit:true}));
|
||||
proc.on('uncaughtException', exitHandler.bind(null, {exit:true}));
|
||||
|
||||
//catches ctrl+c event
|
||||
process.on('SIGINT', exitHandler.bind(null, {sigint:true}));
|
||||
proc.on('SIGINT', exitHandler.bind(null, {sigint:true}));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will instantiate and start a Node, requiring the necessary service
|
||||
* modules, and registering event handlers.
|
||||
* @param {Object} options
|
||||
* @param {String} options.path - The absolute path of the configuration file
|
||||
* @param {Object} options.config - The parsed bitcore-node.json configuration file
|
||||
* @param {Array} options.config.services - An array of services names.
|
||||
* @param {Object} options.config.servicesConfig - Parameters to pass to each service
|
||||
* @param {String} options.config.datadir - A relative (to options.path) or absolute path to the datadir
|
||||
* @param {String} options.config.network - 'livenet', 'testnet' or 'regtest
|
||||
* @param {Number} options.config.port - The port to use for the web service
|
||||
*/
|
||||
function start(options) {
|
||||
|
||||
var fullConfig = _.clone(options.config);
|
||||
fullConfig.services = setupServices(require, options.config);
|
||||
fullConfig.datadir = path.resolve(options.path, options.config.datadir);
|
||||
|
||||
var node = new BitcoreNode(fullConfig);
|
||||
|
||||
// set up the event handlers for logging sync information
|
||||
registerSyncHandlers(node);
|
||||
|
||||
// setup handlers for uncaught exceptions and ctrl+c
|
||||
registerExitHandlers(process, node);
|
||||
|
||||
node.on('ready', function() {
|
||||
log.info('Bitcore Node ready');
|
||||
});
|
||||
|
||||
node.on('error', function(err) {
|
||||
log.error(err);
|
||||
});
|
||||
|
||||
return node;
|
||||
|
||||
}
|
||||
|
||||
module.exports = start;
|
||||
module.exports.registerExitHandlers = registerExitHandlers;
|
||||
module.exports.registerSyncHandlers = registerSyncHandlers;
|
||||
module.exports.setupServices = setupServices;
|
||||
|
|
|
@ -6,6 +6,7 @@ var mkdirp = require('mkdirp');
|
|||
var fs = require('fs');
|
||||
var bitcore = require('bitcore');
|
||||
var $ = bitcore.util.preconditions;
|
||||
var _ = bitcore.deps._;
|
||||
var index = require('../');
|
||||
var log = index.log;
|
||||
var Service = require('../service');
|
||||
|
@ -21,37 +22,14 @@ function Bitcoin(options) {
|
|||
return new Bitcoin(options);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
Service.call(this, options);
|
||||
|
||||
if (Object.keys(this.instances).length) {
|
||||
throw new Error('Bitcoin cannot be instantiated more than once.');
|
||||
}
|
||||
|
||||
$.checkState(this.node.datadir, 'Node is missing datadir property');
|
||||
|
||||
Object.keys(exports).forEach(function(key) {
|
||||
self[key] = exports[key];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
util.inherits(Bitcoin, Service);
|
||||
|
||||
Bitcoin.dependencies = [];
|
||||
|
||||
Bitcoin.instances = {};
|
||||
Bitcoin.prototype.instances = Bitcoin.instances;
|
||||
|
||||
Bitcoin.__defineGetter__('global', function() {
|
||||
return Bitcoin.instances[Object.keys(Bitcoin.instances)[0]];
|
||||
});
|
||||
|
||||
Bitcoin.prototype.__defineGetter__('global', function() {
|
||||
return Bitcoin.global;
|
||||
});
|
||||
|
||||
Bitcoin.DEFAULT_CONFIG = 'whitelist=127.0.0.1\n' + 'txindex=1\n';
|
||||
|
||||
Bitcoin.prototype._loadConfiguration = function() {
|
||||
|
@ -93,16 +71,62 @@ Bitcoin.prototype._loadConfiguration = function() {
|
|||
);
|
||||
};
|
||||
|
||||
Bitcoin.prototype._onTipUpdate = function(result) {
|
||||
if (result) {
|
||||
// Emit and event that the tip was updated
|
||||
this.height = result;
|
||||
this.emit('tip', result);
|
||||
|
||||
// TODO stopping status
|
||||
if(!this.node.stopping) {
|
||||
var percentage = this.syncPercentage();
|
||||
log.info('Bitcoin Core Daemon New Height:', this.height, 'Percentage:', percentage);
|
||||
}
|
||||
|
||||
// Recursively wait until the next update
|
||||
bindings.onTipUpdate(this._onTipUpdate.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
Bitcoin.prototype._registerEventHandlers = function() {
|
||||
var self = this;
|
||||
|
||||
// Set the height and emit a new tip
|
||||
bindings.onTipUpdate(self._onTipUpdate.bind(this));
|
||||
|
||||
// Register callback function to handle incoming transactions
|
||||
bindings.startTxMon(function(txs) {
|
||||
for(var i = 0; i < txs.length; i++) {
|
||||
self.emit('tx', txs[i]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Bitcoin.prototype._onReady = function(result, callback) {
|
||||
var self = this;
|
||||
|
||||
self._registerEventHandlers();
|
||||
|
||||
var info = self.getInfo();
|
||||
self.height = info.blocks;
|
||||
|
||||
self.getBlock(0, function(err, block) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
self.genesisBuffer = block;
|
||||
self.emit('ready', result);
|
||||
log.info('Bitcoin Daemon Ready');
|
||||
setImmediate(callback);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
Bitcoin.prototype.start = function(callback) {
|
||||
var self = this;
|
||||
|
||||
this._loadConfiguration();
|
||||
|
||||
if (this.instances[this.datadir]) {
|
||||
return callback(new Error('Bitcoin already started'));
|
||||
}
|
||||
this.instances[this.datadir] = true;
|
||||
|
||||
bindings.start({
|
||||
datadir: this.node.datadir,
|
||||
network: this.node.network.name
|
||||
|
@ -110,48 +134,12 @@ Bitcoin.prototype.start = function(callback) {
|
|||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self._started = true;
|
||||
|
||||
// Wait until the block chain is ready
|
||||
bindings.onBlocksReady(function(err, result) {
|
||||
|
||||
function onTipUpdateListener(result) {
|
||||
if (result) {
|
||||
// Emit and event that the tip was updated
|
||||
self.height = result;
|
||||
self.emit('tip', result);
|
||||
|
||||
// TODO stopping status
|
||||
if(!self.stopping) {
|
||||
var percentage = self.syncPercentage();
|
||||
log.info('Bitcoin Core Daemon New Height:', self.height, 'Percentage:', percentage);
|
||||
}
|
||||
|
||||
// Recursively wait until the next update
|
||||
bindings.onTipUpdate(onTipUpdateListener);
|
||||
}
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
bindings.onTipUpdate(onTipUpdateListener);
|
||||
|
||||
bindings.startTxMon(function(txs) {
|
||||
for(var i = 0; i < txs.length; i++) {
|
||||
self.emit('tx', txs[i]);
|
||||
}
|
||||
});
|
||||
|
||||
// Set the current chain height
|
||||
var info = self.getInfo();
|
||||
self.height = info.blocks;
|
||||
|
||||
// Get the genesis block
|
||||
self.getBlock(0, function(err, block) {
|
||||
self.genesisBuffer = block;
|
||||
self.emit('ready', result);
|
||||
log.info('Bitcoin Daemon Ready');
|
||||
setImmediate(callback);
|
||||
});
|
||||
|
||||
self._onReady(result, callback);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -100,8 +100,6 @@ DB.prototype.start = function(callback) {
|
|||
// Notify that there is a new tip
|
||||
self.node.services.bitcoind.on('tip', function(height) {
|
||||
if(!self.node.stopping) {
|
||||
var percentage = self.node.services.bitcoind.syncPercentage();
|
||||
log.info('Bitcoin Core Daemon New Height:', height, 'Percentage:', percentage);
|
||||
self.sync();
|
||||
}
|
||||
});
|
||||
|
@ -292,19 +290,21 @@ DB.prototype.getPrevHash = function(blockHash, callback) {
|
|||
|
||||
/**
|
||||
* Saves metadata to the database
|
||||
* @param {Object} metadata - The metadata
|
||||
* @param {Function} callback - A function that accepts: Error
|
||||
*/
|
||||
DB.prototype.putMetadata = function(metadata, callback) {
|
||||
this.store.put('metadata', JSON.stringify(metadata), {}, callback);
|
||||
};
|
||||
|
||||
DB.prototype.saveMetadata = function(callback) {
|
||||
var self = this;
|
||||
|
||||
callback = callback || function() {};
|
||||
function defaultCallback(err) {
|
||||
if (err) {
|
||||
self.emit('error', err);
|
||||
}
|
||||
}
|
||||
|
||||
if(self.lastSavedMetadata && Date.now() < self.lastSavedMetadata.getTime() + self.lastSavedMetadataThreshold) {
|
||||
callback = callback || defaultCallback;
|
||||
|
||||
var threshold = self.lastSavedMetadataThreshold;
|
||||
if (self.lastSavedMetadata && Date.now() < self.lastSavedMetadata.getTime() + threshold) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
|
@ -316,7 +316,8 @@ DB.prototype.saveMetadata = function(callback) {
|
|||
|
||||
self.lastSavedMetadata = new Date();
|
||||
|
||||
self.putMetadata(metadata, callback);
|
||||
this.store.put('metadata', JSON.stringify(metadata), {}, callback);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,7 +4,6 @@ var async = require('async');
|
|||
var levelup = require('levelup');
|
||||
var bitcore = require('bitcore');
|
||||
var Transaction = bitcore.Transaction;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
|
||||
Transaction.prototype.populateInputs = function(db, poolTransactions, callback) {
|
||||
var self = this;
|
||||
|
@ -58,13 +57,4 @@ Transaction.prototype._checkSpent = function(db, input, poolTransactions, callba
|
|||
});
|
||||
};
|
||||
|
||||
Transaction.manyToBuffer = function(transactions) {
|
||||
var bw = new BufferWriter();
|
||||
var count = transactions.length;
|
||||
for(var i = 0; i < count; i++) {
|
||||
transactions[i].toBufferWriter(bw);
|
||||
}
|
||||
return bw.concat();
|
||||
};
|
||||
|
||||
module.exports = Transaction;
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
"upload": "node bin/upload.js",
|
||||
"start": "node bin/start.js",
|
||||
"test": "NODE_ENV=test mocha -R spec --recursive",
|
||||
"coverage": "istanbul cover _mocha -- --recursive",
|
||||
"coverage": "NODE_ENV=test istanbul cover _mocha -- --recursive",
|
||||
"libbitcoind": "node bin/start-libbitcoind.js"
|
||||
},
|
||||
"tags": [
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var defaultConfig = require('../../lib/scaffold/default-config');
|
||||
|
||||
describe('#defaultConfig', function() {
|
||||
|
||||
it('will return expected configuration', function() {
|
||||
var cwd = process.cwd();
|
||||
var home = process.env.HOME;
|
||||
var info = defaultConfig();
|
||||
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']);
|
||||
});
|
||||
|
||||
it('will return expected configuration from environment variables', function() {
|
||||
var cwd = process.cwd();
|
||||
process.env.BITCORENODE_DIR = '/home/bitcore-node/.bitcoin';
|
||||
process.env.BITCORENODE_NETWORK = 'testnet';
|
||||
process.env.BITCORENODE_PORT = 3002;
|
||||
var info = defaultConfig();
|
||||
info.path.should.equal(cwd);
|
||||
info.config.datadir.should.equal('/home/bitcore-node/.bitcoin');
|
||||
info.config.network.should.equal('testnet');
|
||||
info.config.port.should.equal(3002);
|
||||
info.config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']);
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,165 @@
|
|||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var path = require('path');
|
||||
var sinon = require('sinon');
|
||||
var proxyquire = require('proxyquire');
|
||||
var start = require('../../lib/scaffold/start');
|
||||
|
||||
describe('#start', function() {
|
||||
describe('#setupServices', function() {
|
||||
var setupServices = proxyquire('../../lib/scaffold/start', {}).setupServices;
|
||||
it('will require an internal module', function() {
|
||||
function InternalService() {}
|
||||
InternalService.dependencies = [];
|
||||
InternalService.prototype.start = sinon.stub();
|
||||
InternalService.prototype.stop = sinon.stub();
|
||||
var expectedPath = path.resolve(__dirname, '../../lib/services/internal');
|
||||
var testRequire = function(p) {
|
||||
p.should.equal(expectedPath);
|
||||
return InternalService;
|
||||
};
|
||||
var config = {
|
||||
services: ['internal'],
|
||||
servicesConfig: {
|
||||
internal: {
|
||||
param: 'value'
|
||||
}
|
||||
}
|
||||
};
|
||||
var services = setupServices(testRequire, config);
|
||||
services[0].name.should.equal('internal');
|
||||
services[0].config.should.deep.equal({param: 'value'});
|
||||
services[0].module.should.equal(InternalService);
|
||||
});
|
||||
it('will require a local module', function() {
|
||||
function LocalService() {}
|
||||
LocalService.dependencies = [];
|
||||
LocalService.prototype.start = sinon.stub();
|
||||
LocalService.prototype.stop = sinon.stub();
|
||||
var notfoundPath = path.resolve(__dirname, '../../lib/services/local');
|
||||
var testRequire = function(p) {
|
||||
if (p === notfoundPath) {
|
||||
throw new Error();
|
||||
} else if (p === 'local') {
|
||||
return LocalService;
|
||||
} else if (p === 'local/package.json') {
|
||||
return {
|
||||
name: 'local'
|
||||
};
|
||||
}
|
||||
};
|
||||
var config = {
|
||||
services: ['local']
|
||||
};
|
||||
var services = setupServices(testRequire, config);
|
||||
services[0].name.should.equal('local');
|
||||
services[0].module.should.equal(LocalService);
|
||||
});
|
||||
it('will require a local module with "bitcoreNode" in package.json', function() {
|
||||
function LocalService() {}
|
||||
LocalService.dependencies = [];
|
||||
LocalService.prototype.start = sinon.stub();
|
||||
LocalService.prototype.stop = sinon.stub();
|
||||
var notfoundPath = path.resolve(__dirname, '../../lib/services/local');
|
||||
var testRequire = function(p) {
|
||||
if (p === notfoundPath) {
|
||||
throw new Error();
|
||||
} else if (p === 'local/package.json') {
|
||||
return {
|
||||
name: 'local',
|
||||
bitcoreNode: 'lib/bitcoreNode.js'
|
||||
};
|
||||
} else if (p === 'local/lib/bitcoreNode.js') {
|
||||
return LocalService;
|
||||
}
|
||||
};
|
||||
var config = {
|
||||
services: ['local']
|
||||
};
|
||||
var services = setupServices(testRequire, config);
|
||||
services[0].name.should.equal('local');
|
||||
services[0].module.should.equal(LocalService);
|
||||
});
|
||||
it('will throw error if module is incompatible', function() {
|
||||
var internal = {};
|
||||
var testRequire = function() {
|
||||
return internal;
|
||||
};
|
||||
var config = {
|
||||
services: ['bitcoind']
|
||||
};
|
||||
(function() {
|
||||
setupServices(testRequire, config);
|
||||
}).should.throw('Could not load service');
|
||||
});
|
||||
});
|
||||
describe('#registerSyncHandlers', function() {
|
||||
it('will log the sync status at an interval', function(done) {
|
||||
var log = {
|
||||
info: sinon.stub()
|
||||
};
|
||||
var registerSyncHandlers = proxyquire('../../lib/scaffold/start', {
|
||||
'../': {
|
||||
log: log
|
||||
}
|
||||
}).registerSyncHandlers;
|
||||
var node = new EventEmitter();
|
||||
node.services = {
|
||||
db: new EventEmitter()
|
||||
};
|
||||
node.services.db.tip = {
|
||||
hash: 'hash',
|
||||
__height: 10
|
||||
};
|
||||
registerSyncHandlers(node, 10);
|
||||
node.emit('ready');
|
||||
node.services.db.emit('addblock');
|
||||
setTimeout(function() {
|
||||
node.emit('synced');
|
||||
log.info.callCount.should.be.within(3, 4);
|
||||
done();
|
||||
}, 35);
|
||||
});
|
||||
});
|
||||
describe('#registerExitHandlers', function() {
|
||||
var log = {
|
||||
info: sinon.stub(),
|
||||
error: sinon.stub()
|
||||
};
|
||||
var registerExitHandlers = proxyquire('../../lib/scaffold/start', {
|
||||
'../': {
|
||||
log: log
|
||||
}
|
||||
}).registerExitHandlers;
|
||||
it('log, stop and exit with an `uncaughtException`', function(done) {
|
||||
var proc = new EventEmitter();
|
||||
proc.exit = sinon.stub();
|
||||
var node = {
|
||||
stop: sinon.stub().callsArg(0)
|
||||
};
|
||||
registerExitHandlers(proc, node);
|
||||
proc.emit('uncaughtException', new Error('test'));
|
||||
setImmediate(function() {
|
||||
node.stop.callCount.should.equal(1);
|
||||
proc.exit.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('stop and exit on `SIGINT`', function(done) {
|
||||
var proc = new EventEmitter();
|
||||
proc.exit = sinon.stub();
|
||||
var node = {
|
||||
stop: sinon.stub().callsArg(0)
|
||||
};
|
||||
registerExitHandlers(proc, node);
|
||||
proc.emit('SIGINT');
|
||||
setImmediate(function() {
|
||||
node.stop.callCount.should.equal(1);
|
||||
proc.exit.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,17 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var sinon = require('sinon');
|
||||
var proxyquire = require('proxyquire');
|
||||
var fs = require('fs');
|
||||
var sinon = require('sinon');
|
||||
var readFileSync = sinon.stub().returns(fs.readFileSync(__dirname + '/../data/bitcoin.conf'));
|
||||
var BitcoinService = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: sinon.stub().returns(fs.readFileSync(__dirname + '/../data/bitcoin.conf'))
|
||||
}
|
||||
});
|
||||
var BadBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: sinon.stub().returns(fs.readFileSync(__dirname + '/../data/badbitcoin.conf'))
|
||||
readFileSync: readFileSync
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -24,9 +21,33 @@ describe('Bitcoin Service', function() {
|
|||
}
|
||||
}
|
||||
};
|
||||
describe('@constructor', function() {
|
||||
it('will create an instance', function() {
|
||||
var bitcoind = new BitcoinService(baseConfig);
|
||||
should.exist(bitcoind);
|
||||
});
|
||||
it('will create an instance without `new`', function() {
|
||||
var bitcoind = BitcoinService(baseConfig);
|
||||
should.exist(bitcoind);
|
||||
});
|
||||
});
|
||||
describe('@dependencies', function() {
|
||||
it('will have no dependencies', function() {
|
||||
BitcoinService.dependencies.should.deep.equal([]);
|
||||
});
|
||||
});
|
||||
describe('#_loadConfiguration', function() {
|
||||
it('will parse a bitcoin.conf file', function() {
|
||||
var bitcoind = new BitcoinService(baseConfig);
|
||||
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: readFileSync,
|
||||
existsSync: sinon.stub().returns(true)
|
||||
},
|
||||
mkdirp: {
|
||||
sync: sinon.stub()
|
||||
}
|
||||
});
|
||||
var bitcoind = new TestBitcoin(baseConfig);
|
||||
bitcoind._loadConfiguration({datadir: process.env.HOME + '/.bitcoin'});
|
||||
should.exist(bitcoind.configuration);
|
||||
bitcoind.configuration.should.deep.equal({
|
||||
|
@ -40,11 +61,283 @@ describe('Bitcoin Service', function() {
|
|||
});
|
||||
});
|
||||
it('should throw an exception if txindex isn\'t enabled in the configuration', function() {
|
||||
var bitcoind = new BadBitcoin(baseConfig);
|
||||
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: sinon.stub().returns(fs.readFileSync(__dirname + '/../data/badbitcoin.conf')),
|
||||
existsSync: sinon.stub().returns(true),
|
||||
},
|
||||
mkdirp: {
|
||||
sync: sinon.stub()
|
||||
}
|
||||
});
|
||||
var bitcoind = new TestBitcoin(baseConfig);
|
||||
(function() {
|
||||
bitcoind._loadConfiguration({datadir: './test'});
|
||||
}).should.throw('Txindex option');
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#_registerEventHandlers', function() {
|
||||
it('will emit tx with transactions from bindings', function(done) {
|
||||
var transaction = {};
|
||||
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: readFileSync
|
||||
},
|
||||
bindings: function(name) {
|
||||
name.should.equal('bitcoind.node');
|
||||
return {
|
||||
onTipUpdate: sinon.stub(),
|
||||
startTxMon: sinon.stub().callsArgWith(0, [transaction])
|
||||
};
|
||||
}
|
||||
});
|
||||
var bitcoind = new TestBitcoin(baseConfig);
|
||||
bitcoind.on('tx', function(tx) {
|
||||
tx.should.equal(transaction);
|
||||
done();
|
||||
});
|
||||
bitcoind._registerEventHandlers();
|
||||
});
|
||||
it('will emit tip from bindings', function(done) {
|
||||
var height = 1;
|
||||
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: readFileSync
|
||||
},
|
||||
bindings: function(name) {
|
||||
name.should.equal('bitcoind.node');
|
||||
return {
|
||||
syncPercentage: function() {
|
||||
return height * 10;
|
||||
},
|
||||
onTipUpdate: function(callback) {
|
||||
if (height >= 10) {
|
||||
return callback(undefined);
|
||||
}
|
||||
setImmediate(function() {
|
||||
callback(height++);
|
||||
});
|
||||
},
|
||||
startTxMon: sinon.stub()
|
||||
};
|
||||
}
|
||||
});
|
||||
var bitcoind = new TestBitcoin(baseConfig);
|
||||
var tipCallCount = 0;
|
||||
bitcoind.on('tip', function(height) {
|
||||
should.exist(height);
|
||||
tipCallCount++;
|
||||
if (height === 9) {
|
||||
tipCallCount.should.equal(9);
|
||||
done();
|
||||
}
|
||||
});
|
||||
bitcoind._registerEventHandlers();
|
||||
});
|
||||
});
|
||||
describe('#_onReady', function(done) {
|
||||
var genesisBuffer = new Buffer('0100000043497fd7f826957108f4a30fd9cec3aeba79972084e90ead01ea330900000000bac8b0fa927c0ac8234287e33c5f74d38d354820e24756ad709d7038fc5f31f020e7494dffff001d03e4b6720101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0420e7494d017f062f503253482fffffffff0100f2052a010000002321021aeaf2f8638a129a3156fbe7e5ef635226b0bafd495ff03afe2c843d7e3a4b51ac00000000', 'hex');
|
||||
it('will emit ready and set the height and genesisBuffer', function(done) {
|
||||
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: readFileSync
|
||||
},
|
||||
bindings: function(name) {
|
||||
name.should.equal('bitcoind.node');
|
||||
return {
|
||||
onTipUpdate: sinon.stub(),
|
||||
startTxMon: sinon.stub(),
|
||||
getInfo: sinon.stub().returns({
|
||||
blocks: 101
|
||||
}),
|
||||
getBlock: sinon.stub().callsArgWith(1, null, genesisBuffer)
|
||||
};
|
||||
}
|
||||
});
|
||||
var bitcoind = new TestBitcoin(baseConfig);
|
||||
bitcoind._registerEventHandlers = sinon.stub();
|
||||
var result = {};
|
||||
var readyCallCount = 0;
|
||||
bitcoind.on('ready', function() {
|
||||
readyCallCount++;
|
||||
});
|
||||
bitcoind._onReady(result, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
bitcoind._registerEventHandlers.callCount.should.equal(1);
|
||||
readyCallCount.should.equal(1);
|
||||
bitcoind.genesisBuffer.should.equal(genesisBuffer);
|
||||
bitcoind.height.should.equal(101);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#start', function() {
|
||||
it('call bindings start with the correct arguments', function(done) {
|
||||
var startCallCount = 0;
|
||||
var start = function(obj, cb) {
|
||||
startCallCount++;
|
||||
obj.datadir.should.equal('testdir');
|
||||
obj.network.should.equal('regtest');
|
||||
cb();
|
||||
};
|
||||
var onBlocksReady = sinon.stub().callsArg(0);
|
||||
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: readFileSync
|
||||
},
|
||||
bindings: function(name) {
|
||||
name.should.equal('bitcoind.node');
|
||||
return {
|
||||
start: start,
|
||||
onBlocksReady: onBlocksReady
|
||||
};
|
||||
}
|
||||
});
|
||||
var bitcoind = new TestBitcoin(baseConfig);
|
||||
bitcoind._loadConfiguration = sinon.stub();
|
||||
bitcoind._onReady = sinon.stub().callsArg(1);
|
||||
bitcoind.start(function(err) {
|
||||
should.not.exist(err);
|
||||
bitcoind._loadConfiguration.callCount.should.equal(1);
|
||||
startCallCount.should.equal(1);
|
||||
onBlocksReady.callCount.should.equal(1);
|
||||
bitcoind._onReady.callCount.should.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('will give an error from bindings.start', function(done) {
|
||||
var start = sinon.stub().callsArgWith(1, new Error('test'));
|
||||
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: readFileSync
|
||||
},
|
||||
bindings: function(name) {
|
||||
name.should.equal('bitcoind.node');
|
||||
return {
|
||||
start: start
|
||||
};
|
||||
}
|
||||
});
|
||||
var bitcoind = new TestBitcoin(baseConfig);
|
||||
bitcoind._loadConfiguration = sinon.stub();
|
||||
bitcoind.start(function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('will give an error from bindings.onBlocksReady', function(done) {
|
||||
var start = sinon.stub().callsArgWith(1, null);
|
||||
var onBlocksReady = sinon.stub().callsArgWith(0, new Error('test'));
|
||||
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: readFileSync
|
||||
},
|
||||
bindings: function(name) {
|
||||
name.should.equal('bitcoind.node');
|
||||
return {
|
||||
start: start,
|
||||
onBlocksReady: onBlocksReady
|
||||
};
|
||||
}
|
||||
});
|
||||
var bitcoind = new TestBitcoin(baseConfig);
|
||||
bitcoind._onReady = sinon.stub().callsArg(1);
|
||||
bitcoind._loadConfiguration = sinon.stub();
|
||||
bitcoind.start(function(err) {
|
||||
should.exist(err);
|
||||
err.message.should.equal('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#stop', function() {
|
||||
it('will call bindings stop', function() {
|
||||
var stop = sinon.stub().callsArgWith(0, null, 'status');
|
||||
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: readFileSync
|
||||
},
|
||||
bindings: function(name) {
|
||||
name.should.equal('bitcoind.node');
|
||||
return {
|
||||
stop: stop
|
||||
};
|
||||
}
|
||||
});
|
||||
var bitcoind = new TestBitcoin(baseConfig);
|
||||
bitcoind.stop(function(err, status) {
|
||||
stop.callCount.should.equal(1);
|
||||
should.not.exist(err);
|
||||
});
|
||||
});
|
||||
it('will give an error from bindings stop', function() {
|
||||
var stop = sinon.stub().callsArgWith(0, new Error('test'));
|
||||
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: readFileSync
|
||||
},
|
||||
bindings: function(name) {
|
||||
name.should.equal('bitcoind.node');
|
||||
return {
|
||||
stop: stop
|
||||
};
|
||||
}
|
||||
});
|
||||
var bitcoind = new TestBitcoin(baseConfig);
|
||||
bitcoind.stop(function(err) {
|
||||
stop.callCount.should.equal(1);
|
||||
should.exist(err);
|
||||
err.message.should.equal('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('proxy methods', function() {
|
||||
|
||||
var proxyMethods = [
|
||||
['isSynced', 0],
|
||||
['syncPercentage', 0],
|
||||
['getBlock', 2],
|
||||
['isSpent', 2],
|
||||
['getBlockIndex', 1],
|
||||
['estimateFee', 1],
|
||||
['sendTransaction', 2],
|
||||
['getTransaction', 3],
|
||||
['getTransactionWithBlockInfo', 3],
|
||||
['getMempoolOutputs', 1],
|
||||
['addMempoolUncheckedTransaction', 1],
|
||||
['getInfo', 0]
|
||||
];
|
||||
|
||||
proxyMethods.forEach(function(x) {
|
||||
it('pass ' + x[1] + ' argument(s) to ' + x[0], function() {
|
||||
|
||||
var stub = sinon.stub();
|
||||
var TestBitcoin = proxyquire('../../lib/services/bitcoind', {
|
||||
fs: {
|
||||
readFileSync: readFileSync
|
||||
},
|
||||
bindings: function(name) {
|
||||
name.should.equal('bitcoind.node');
|
||||
var methods = {};
|
||||
methods[x[0]] = stub;
|
||||
return methods;
|
||||
}
|
||||
});
|
||||
|
||||
var bitcoind = new TestBitcoin(baseConfig);
|
||||
var args = [];
|
||||
for (var i = 0; i < x[1]; i++) {
|
||||
args.push(i);
|
||||
}
|
||||
|
||||
bitcoind[x[0]].apply(bitcoind, args);
|
||||
stub.callCount.should.equal(1);
|
||||
stub.args[0].length.should.equal(x[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ var chainHashes = require('../data/hashes.json');
|
|||
var chainData = require('../data/testnet-blocks.json');
|
||||
var errors = index.errors;
|
||||
var memdown = require('memdown');
|
||||
var levelup = require('levelup');
|
||||
var bitcore = require('bitcore');
|
||||
var Transaction = bitcore.Transaction;
|
||||
|
||||
|
@ -237,7 +238,6 @@ describe('DB Service', function() {
|
|||
var db = new TestDB(baseConfig);
|
||||
db.node.services = {};
|
||||
db.node.services.bitcoind = new EventEmitter();
|
||||
db.node.services.bitcoind.syncPercentage = sinon.spy();
|
||||
db.node.services.bitcoind.genesisBuffer = genesisBuffer;
|
||||
db.getMetadata = sinon.stub().callsArg(0);
|
||||
db.connectBlock = sinon.stub().callsArg(1);
|
||||
|
@ -245,7 +245,6 @@ describe('DB Service', function() {
|
|||
db.sync = sinon.stub();
|
||||
db.start(function() {
|
||||
db.sync = function() {
|
||||
db.node.services.bitcoind.syncPercentage.callCount.should.equal(1);
|
||||
done();
|
||||
};
|
||||
db.node.services.bitcoind.emit('tip', 10);
|
||||
|
@ -466,7 +465,7 @@ describe('DB Service', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#estimateFee", function() {
|
||||
describe('#estimateFee', function() {
|
||||
it('should pass along the fee from bitcoind', function(done) {
|
||||
var db = new DB(baseConfig);
|
||||
db.node = {};
|
||||
|
@ -484,6 +483,140 @@ describe('DB Service', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#saveMetadata', function() {
|
||||
it('will emit an error with default callback', function(done) {
|
||||
var db = new DB(baseConfig);
|
||||
db.cache = {
|
||||
hashes: {},
|
||||
chainHashes: {}
|
||||
};
|
||||
db.tip = {
|
||||
hash: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
|
||||
__height: 0
|
||||
};
|
||||
db.store = {
|
||||
put: sinon.stub().callsArgWith(3, new Error('test'))
|
||||
};
|
||||
db.on('error', function(err) {
|
||||
err.message.should.equal('test');
|
||||
done();
|
||||
});
|
||||
db.saveMetadata();
|
||||
});
|
||||
it('will give an error with callback', function(done) {
|
||||
var db = new DB(baseConfig);
|
||||
db.cache = {
|
||||
hashes: {},
|
||||
chainHashes: {}
|
||||
};
|
||||
db.tip = {
|
||||
hash: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
|
||||
__height: 0
|
||||
};
|
||||
db.store = {
|
||||
put: sinon.stub().callsArgWith(3, new Error('test'))
|
||||
};
|
||||
db.saveMetadata(function(err) {
|
||||
err.message.should.equal('test');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('will call store with the correct arguments', function(done) {
|
||||
var db = new DB(baseConfig);
|
||||
db.cache = {
|
||||
hashes: {},
|
||||
chainHashes: {}
|
||||
};
|
||||
db.tip = {
|
||||
hash: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
|
||||
__height: 0
|
||||
};
|
||||
db.store = {
|
||||
put: function(key, value, options, callback) {
|
||||
key.should.equal('metadata');
|
||||
JSON.parse(value).should.deep.equal({
|
||||
tip: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
|
||||
tipHeight: 0,
|
||||
cache: {
|
||||
hashes: {},
|
||||
chainHashes: {}
|
||||
}
|
||||
});
|
||||
options.should.deep.equal({});
|
||||
callback.should.be.a('function');
|
||||
done();
|
||||
}
|
||||
};
|
||||
db.saveMetadata();
|
||||
});
|
||||
it('will not call store with threshold', function(done) {
|
||||
var db = new DB(baseConfig);
|
||||
db.lastSavedMetadata = new Date();
|
||||
db.lastSavedMetadataThreshold = 30000;
|
||||
var put = sinon.stub();
|
||||
db.store = {
|
||||
put: put
|
||||
};
|
||||
db.saveMetadata(function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
put.callCount.should.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getMetadata', function() {
|
||||
it('will get metadata', function() {
|
||||
var db = new DB(baseConfig);
|
||||
var json = JSON.stringify({
|
||||
tip: '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
|
||||
tipHeight: 101,
|
||||
cache: {
|
||||
hashes: {},
|
||||
chainHashes: {}
|
||||
}
|
||||
});
|
||||
db.store = {};
|
||||
db.store.get = sinon.stub().callsArgWith(2, null, json);
|
||||
db.getMetadata(function(err, data) {
|
||||
data.tip.should.equal('000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f');
|
||||
data.tipHeight.should.equal(101);
|
||||
data.cache.should.deep.equal({
|
||||
hashes: {},
|
||||
chainHashes: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
it('will handle a notfound error from leveldb', function() {
|
||||
var db = new DB(baseConfig);
|
||||
db.store = {};
|
||||
var error = new levelup.errors.NotFoundError();
|
||||
db.store.get = sinon.stub().callsArgWith(2, error);
|
||||
db.getMetadata(function(err, data) {
|
||||
should.not.exist(err);
|
||||
data.should.deep.equal({});
|
||||
});
|
||||
});
|
||||
it('will handle error from leveldb', function() {
|
||||
var db = new DB(baseConfig);
|
||||
db.store = {};
|
||||
db.store.get = sinon.stub().callsArgWith(2, new Error('test'));
|
||||
db.getMetadata(function(err) {
|
||||
err.message.should.equal('test');
|
||||
});
|
||||
});
|
||||
it('give an error when parsing invalid json', function() {
|
||||
var db = new DB(baseConfig);
|
||||
db.store = {};
|
||||
db.store.get = sinon.stub().callsArgWith(2, null, '{notvalid@json}');
|
||||
db.getMetadata(function(err) {
|
||||
err.message.should.equal('Could not parse metadata');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#connectBlock', function() {
|
||||
it('should remove block from mempool and call blockHandler with true', function(done) {
|
||||
var db = new DB(baseConfig);
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var utils = require('../lib/utils');
|
||||
|
||||
describe('Utils', function() {
|
||||
|
||||
describe('#isHash', function() {
|
||||
|
||||
it('false for short string', function() {
|
||||
var a = utils.isHash('ashortstring');
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for long string', function() {
|
||||
var a = utils.isHash('00000000000000000000000000000000000000000000000000000000000000000');
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for correct length invalid char', function() {
|
||||
var a = utils.isHash('z000000000000000000000000000000000000000000000000000000000000000');
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for invalid type (buffer)', function() {
|
||||
var a = utils.isHash(new Buffer('abcdef', 'hex'));
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for invalid type (number)', function() {
|
||||
var a = utils.isHash(123456);
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('true for hash', function() {
|
||||
var a = utils.isHash('fc63629e2106c3440d7e56751adc8cfa5266a5920c1b54b81565af25aec1998b');
|
||||
a.should.equal(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#isSafeNatural', function() {
|
||||
|
||||
it('false for float', function() {
|
||||
var a = utils.isSafeNatural(0.1);
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for string float', function() {
|
||||
var a = utils.isSafeNatural('0.1');
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for string integer', function() {
|
||||
var a = utils.isSafeNatural('1');
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for negative integer', function() {
|
||||
var a = utils.isSafeNatural(-1);
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for negative integer string', function() {
|
||||
var a = utils.isSafeNatural('-1');
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for infinity', function() {
|
||||
var a = utils.isSafeNatural(Infinity);
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for NaN', function() {
|
||||
var a = utils.isSafeNatural(NaN);
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('false for unsafe number', function() {
|
||||
var a = utils.isSafeNatural(Math.pow(2, 53));
|
||||
a.should.equal(false);
|
||||
});
|
||||
|
||||
it('true for positive integer', function() {
|
||||
var a = utils.isSafeNatural(1000);
|
||||
a.should.equal(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#startAtZero', function() {
|
||||
|
||||
it('will set key to zero if not set', function() {
|
||||
var obj = {};
|
||||
utils.startAtZero(obj, 'key');
|
||||
obj.key.should.equal(0);
|
||||
});
|
||||
|
||||
it('not if already set', function() {
|
||||
var obj = {
|
||||
key: 10
|
||||
};
|
||||
utils.startAtZero(obj, 'key');
|
||||
obj.key.should.equal(10);
|
||||
});
|
||||
|
||||
it('not if set to false', function() {
|
||||
var obj = {
|
||||
key: false
|
||||
};
|
||||
utils.startAtZero(obj, 'key');
|
||||
obj.key.should.equal(false);
|
||||
});
|
||||
|
||||
it('not if set to undefined', function() {
|
||||
var obj = {
|
||||
key: undefined
|
||||
};
|
||||
utils.startAtZero(obj, 'key');
|
||||
should.equal(obj.key, undefined);
|
||||
});
|
||||
|
||||
it('not if set to null', function() {
|
||||
var obj = {
|
||||
key: null
|
||||
};
|
||||
utils.startAtZero(obj, 'key');
|
||||
should.equal(obj.key, null);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue