Merge pull request #165 from pnagurny/feature/web-service
Web Service and other changes in preparation for insight-api
This commit is contained in:
commit
f91fbe5e24
@ -69,17 +69,17 @@ describe('Node Functionality', function() {
|
|||||||
{
|
{
|
||||||
name: 'db',
|
name: 'db',
|
||||||
module: DBService,
|
module: DBService,
|
||||||
dependencies: DBService.dependencies
|
config: {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'bitcoind',
|
name: 'bitcoind',
|
||||||
module: BitcoinService,
|
module: BitcoinService,
|
||||||
dependencies: BitcoinService.dependencies
|
config: {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'address',
|
name: 'address',
|
||||||
module: AddressService,
|
module: AddressService,
|
||||||
dependencies: AddressService.dependencies
|
config: {}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -227,7 +227,27 @@ describe('Daemon Binding Functionality', function() {
|
|||||||
work.cmp(expectedWork).should.equal(0);
|
work.cmp(expectedWork).should.equal(0);
|
||||||
expectedWork = expectedWork.add(new BN(2));
|
expectedWork = expectedWork.add(new BN(2));
|
||||||
should.exist(blockIndex.prevHash);
|
should.exist(blockIndex.prevHash);
|
||||||
|
blockIndex.hash.should.equal(blockHashes[i]);
|
||||||
blockIndex.prevHash.should.equal(blockHashes[i - 1]);
|
blockIndex.prevHash.should.equal(blockHashes[i - 1]);
|
||||||
|
blockIndex.height.should.equal(i + 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get block index by height', function() {
|
||||||
|
var expectedWork = new BN(6);
|
||||||
|
[2,3,4,5,6,7,8,9].forEach(function(i) {
|
||||||
|
it('generate block ' + i, function() {
|
||||||
|
var blockIndex = bitcoind.getBlockIndex(i);
|
||||||
|
should.exist(blockIndex);
|
||||||
|
should.exist(blockIndex.chainWork);
|
||||||
|
var work = new BN(blockIndex.chainWork, 'hex');
|
||||||
|
work.cmp(expectedWork).should.equal(0);
|
||||||
|
expectedWork = expectedWork.add(new BN(2));
|
||||||
|
should.exist(blockIndex.prevHash);
|
||||||
|
blockIndex.hash.should.equal(blockHashes[i - 1]);
|
||||||
|
blockIndex.prevHash.should.equal(blockHashes[i - 2]);
|
||||||
|
blockIndex.height.should.equal(i);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
17
lib/node.js
17
lib/node.js
@ -6,10 +6,12 @@ var async = require('async');
|
|||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var Networks = bitcore.Networks;
|
var Networks = bitcore.Networks;
|
||||||
var $ = bitcore.util.preconditions;
|
var $ = bitcore.util.preconditions;
|
||||||
|
var _ = bitcore.deps._;
|
||||||
var index = require('./');
|
var index = require('./');
|
||||||
var log = index.log;
|
var log = index.log;
|
||||||
var Bus = require('./bus');
|
var Bus = require('./bus');
|
||||||
var BaseService = require('./service');
|
var BaseService = require('./service');
|
||||||
|
var errors = require('./errors');
|
||||||
|
|
||||||
function Node(config) {
|
function Node(config) {
|
||||||
if(!(this instanceof Node)) {
|
if(!(this instanceof Node)) {
|
||||||
@ -18,6 +20,8 @@ function Node(config) {
|
|||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
this.errors = errors; // So services can use errors without having to have bitcore-node as a dependency
|
||||||
|
this.log = log;
|
||||||
this.network = null;
|
this.network = null;
|
||||||
this.services = {};
|
this.services = {};
|
||||||
this._unloadedServices = [];
|
this._unloadedServices = [];
|
||||||
@ -30,6 +34,7 @@ function Node(config) {
|
|||||||
|
|
||||||
$.checkState(config.datadir, 'Node config expects "datadir"');
|
$.checkState(config.datadir, 'Node config expects "datadir"');
|
||||||
this.datadir = config.datadir;
|
this.datadir = config.datadir;
|
||||||
|
this.port = config.port;
|
||||||
|
|
||||||
this._setNetwork(config);
|
this._setNetwork(config);
|
||||||
|
|
||||||
@ -116,7 +121,7 @@ Node.prototype.getServiceOrder = function() {
|
|||||||
$.checkState(service, 'Required dependency "' + name + '" not available.');
|
$.checkState(service, 'Required dependency "' + name + '" not available.');
|
||||||
|
|
||||||
// first add the dependencies
|
// first add the dependencies
|
||||||
addToStack(service.dependencies);
|
addToStack(service.module.dependencies);
|
||||||
|
|
||||||
// add to the stack if it hasn't been added
|
// add to the stack if it hasn't been added
|
||||||
if(!stackNames[name]) {
|
if(!stackNames[name]) {
|
||||||
@ -134,9 +139,13 @@ Node.prototype.getServiceOrder = function() {
|
|||||||
|
|
||||||
Node.prototype._instantiateService = function(service) {
|
Node.prototype._instantiateService = function(service) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var mod = new service.module({
|
|
||||||
node: this
|
$.checkState(_.isObject(service.config));
|
||||||
});
|
$.checkState(!service.config.node);
|
||||||
|
|
||||||
|
var config = service.config;
|
||||||
|
config.node = this;
|
||||||
|
var mod = new service.module(config);
|
||||||
|
|
||||||
$.checkState(
|
$.checkState(
|
||||||
mod instanceof BaseService,
|
mod instanceof BaseService,
|
||||||
|
|||||||
@ -13,7 +13,7 @@ function getDefaultConfig() {
|
|||||||
datadir: process.env.BITCORENODE_DIR || path.resolve(process.env.HOME, '.bitcoin'),
|
datadir: process.env.BITCORENODE_DIR || path.resolve(process.env.HOME, '.bitcoin'),
|
||||||
network: process.env.BITCORENODE_NETWORK || 'livenet',
|
network: process.env.BITCORENODE_NETWORK || 'livenet',
|
||||||
port: process.env.BITCORENODE_PORT || 3001,
|
port: process.env.BITCORENODE_PORT || 3001,
|
||||||
services: ['bitcoind', 'db', 'address']
|
services: ['bitcoind', 'db', 'address', 'web']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ var BitcoreNode = require('../node');
|
|||||||
var index = require('../');
|
var index = require('../');
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var _ = bitcore.deps._;
|
var _ = bitcore.deps._;
|
||||||
|
var $ = bitcore.util.preconditions;
|
||||||
var log = index.log;
|
var log = index.log;
|
||||||
log.debug = function() {};
|
log.debug = function() {};
|
||||||
|
|
||||||
@ -22,37 +23,35 @@ function start(options) {
|
|||||||
|
|
||||||
if (config.services) {
|
if (config.services) {
|
||||||
for (var i = 0; i < config.services.length; i++) {
|
for (var i = 0; i < config.services.length; i++) {
|
||||||
var serviceName = config.services[i];
|
var service = {};
|
||||||
var service;
|
service.name = config.services[i];
|
||||||
|
service.config = config.servicesConfig && config.servicesConfig[service.name] ? config.servicesConfig[service.name] : {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// first try in the built-in bitcore-node services directory
|
// first try in the built-in bitcore-node services directory
|
||||||
service = require(path.resolve(__dirname, '../services/' + serviceName));
|
service.module = require(path.resolve(__dirname, '../services/' + service.name));
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
|
||||||
// check if the package.json specifies a specific file to use
|
// check if the package.json specifies a specific file to use
|
||||||
var servicePackage = require(serviceName + '/package.json');
|
var servicePackage = require(service.name + '/package.json');
|
||||||
var serviceModule = serviceName;
|
var serviceModule = service.name;
|
||||||
if (servicePackage.bitcoreNode) {
|
if (servicePackage.bitcoreNode) {
|
||||||
serviceModule = serviceName + '/' + servicePackage.bitcoreNode;
|
serviceModule = service.name + '/' + servicePackage.bitcoreNode;
|
||||||
}
|
}
|
||||||
service = require(serviceModule);
|
service.module = require(serviceModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that the service supports expected methods
|
// check that the service supports expected methods
|
||||||
if (!service.prototype ||
|
if (!service.module.prototype ||
|
||||||
!service.dependencies ||
|
!service.module.dependencies ||
|
||||||
!service.prototype.start ||
|
!service.module.prototype.start ||
|
||||||
!service.prototype.stop) {
|
!service.module.prototype.stop) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Could not load service "' + serviceName + '" as it does not support necessary methods.'
|
'Could not load service "' + service.name + '" as it does not support necessary methods.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
services.push({
|
|
||||||
name: serviceName,
|
|
||||||
module: service,
|
|
||||||
dependencies: service.dependencies
|
|
||||||
});
|
|
||||||
|
|
||||||
|
services.push(service);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,102 +81,7 @@ function start(options) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
node.on('ready', function() {
|
node.on('ready', function() {
|
||||||
|
log.info('Bitcore Node ready');
|
||||||
var io = socketio(fullConfig.port);
|
|
||||||
|
|
||||||
io.on('connection', function(socket) {
|
|
||||||
|
|
||||||
var bus = node.openBus();
|
|
||||||
|
|
||||||
var methods = node.getAllAPIMethods();
|
|
||||||
var methodsMap = {};
|
|
||||||
|
|
||||||
methods.forEach(function(data) {
|
|
||||||
var name = data[0];
|
|
||||||
var instance = data[1];
|
|
||||||
var method = data[2];
|
|
||||||
var args = data[3];
|
|
||||||
methodsMap[name] = {
|
|
||||||
fn: function() {
|
|
||||||
return method.apply(instance, arguments);
|
|
||||||
},
|
|
||||||
args: args
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('message', function(message, socketCallback) {
|
|
||||||
if (methodsMap[message.method]) {
|
|
||||||
var params = message.params;
|
|
||||||
|
|
||||||
if(!params || !params.length) {
|
|
||||||
params = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(params.length !== methodsMap[message.method].args) {
|
|
||||||
return socketCallback({
|
|
||||||
error: {
|
|
||||||
message: 'Expected ' + methodsMap[message.method].args + ' parameters'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var callback = function(err, result) {
|
|
||||||
var response = {};
|
|
||||||
if(err) {
|
|
||||||
response.error = {
|
|
||||||
message: err.toString()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(result) {
|
|
||||||
response.result = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
socketCallback(response);
|
|
||||||
};
|
|
||||||
|
|
||||||
params = params.concat(callback);
|
|
||||||
methodsMap[message.method].fn.apply(this, params);
|
|
||||||
} else {
|
|
||||||
socketCallback({
|
|
||||||
error: {
|
|
||||||
message: 'Method Not Found'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('subscribe', function(name, params) {
|
|
||||||
bus.subscribe(name, params);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('unsubscribe', function(name, params) {
|
|
||||||
bus.unsubscribe(name, params);
|
|
||||||
});
|
|
||||||
|
|
||||||
var events = node.getAllPublishEvents();
|
|
||||||
|
|
||||||
events.forEach(function(event) {
|
|
||||||
bus.on(event.name, function() {
|
|
||||||
if(socket.connected) {
|
|
||||||
var results = [];
|
|
||||||
|
|
||||||
for(var i = 0; i < arguments.length; i++) {
|
|
||||||
results.push(arguments[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var params = [event.name].concat(results);
|
|
||||||
socket.emit.apply(socket, params);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('disconnect', function() {
|
|
||||||
bus.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
node.on('error', function(err) {
|
node.on('error', function(err) {
|
||||||
@ -207,7 +111,12 @@ function start(options) {
|
|||||||
if(err.stack) {
|
if(err.stack) {
|
||||||
console.log(err.stack);
|
console.log(err.stack);
|
||||||
}
|
}
|
||||||
process.exit(-1);
|
node.stop(function(err) {
|
||||||
|
if(err) {
|
||||||
|
log.error('Failed to stop services: ' + err);
|
||||||
|
}
|
||||||
|
process.exit(-1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (options.sigint) {
|
if (options.sigint) {
|
||||||
node.stop(function(err) {
|
node.stop(function(err) {
|
||||||
|
|||||||
@ -24,7 +24,9 @@ Service.dependencies = [];
|
|||||||
*/
|
*/
|
||||||
Service.prototype.blockHandler = function(block, add, callback) {
|
Service.prototype.blockHandler = function(block, add, callback) {
|
||||||
// implement in the child class
|
// implement in the child class
|
||||||
setImmediate(callback);
|
setImmediate(function() {
|
||||||
|
callback(null, []);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,12 +59,26 @@ Service.prototype.getAPIMethods = function() {
|
|||||||
//
|
//
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function which is called when module is first initialized
|
||||||
|
*/
|
||||||
Service.prototype.start = function(done) {
|
Service.prototype.start = function(done) {
|
||||||
setImmediate(done);
|
setImmediate(done);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to be called when bitcore-node is stopped
|
||||||
|
*/
|
||||||
Service.prototype.stop = function(done) {
|
Service.prototype.stop = function(done) {
|
||||||
setImmediate(done);
|
setImmediate(done);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup express routes
|
||||||
|
* @param {Express} app
|
||||||
|
*/
|
||||||
|
Service.prototype.setupRoutes = function(app) {
|
||||||
|
// Setup express routes here
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = Service;
|
module.exports = Service;
|
||||||
|
|||||||
@ -505,6 +505,7 @@ AddressService.prototype.getAddressHistoryForAddress = function(address, queryMe
|
|||||||
var confirmations = 0;
|
var confirmations = 0;
|
||||||
if(transaction.__height >= 0) {
|
if(transaction.__height >= 0) {
|
||||||
confirmations = self.node.services.db.tip.__height - transaction.__height;
|
confirmations = self.node.services.db.tip.__height - transaction.__height;
|
||||||
|
confirmations = self.node.services.db.tip.__height - transaction.__height + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
txinfos[transaction.hash] = {
|
txinfos[transaction.hash] = {
|
||||||
|
|||||||
@ -217,6 +217,7 @@ DB.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tx = Transaction().fromBuffer(obj.buffer);
|
var tx = Transaction().fromBuffer(obj.buffer);
|
||||||
|
tx.__blockHash = obj.blockHash;
|
||||||
tx.__height = obj.height;
|
tx.__height = obj.height;
|
||||||
tx.__timestamp = obj.timestamp;
|
tx.__timestamp = obj.timestamp;
|
||||||
|
|
||||||
@ -657,6 +658,7 @@ DB.prototype.sync = function() {
|
|||||||
|
|
||||||
self.bitcoindSyncing = false;
|
self.bitcoindSyncing = false;
|
||||||
self.lastSavedMetadataThreshold = 0;
|
self.lastSavedMetadataThreshold = 0;
|
||||||
|
self.saveMetadata();
|
||||||
|
|
||||||
// If bitcoind is completely synced
|
// If bitcoind is completely synced
|
||||||
if (self.node.services.bitcoind.isSynced()) {
|
if (self.node.services.bitcoind.isSynced()) {
|
||||||
|
|||||||
155
lib/services/web.js
Normal file
155
lib/services/web.js
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var http = require('http');
|
||||||
|
var express = require('express');
|
||||||
|
var bodyParser = require('body-parser');
|
||||||
|
var socketio = require('socket.io');
|
||||||
|
var BaseService = require('../service');
|
||||||
|
var inherits = require('util').inherits;
|
||||||
|
|
||||||
|
var WebService = function(options) {
|
||||||
|
var self = this;
|
||||||
|
this.node = options.node;
|
||||||
|
this.port = options.port || this.node.port || 3456;
|
||||||
|
|
||||||
|
this.node.on('ready', function() {
|
||||||
|
self.setupAllRoutes();
|
||||||
|
self.server.listen(self.port);
|
||||||
|
self.createMethodsMap();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
inherits(WebService, BaseService);
|
||||||
|
|
||||||
|
WebService.dependencies = [];
|
||||||
|
|
||||||
|
WebService.prototype.start = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
this.app = express();
|
||||||
|
this.app.use(bodyParser.json());
|
||||||
|
|
||||||
|
this.server = http.createServer(this.app);
|
||||||
|
|
||||||
|
this.io = socketio.listen(this.server);
|
||||||
|
this.io.on('connection', this.socketHandler.bind(this));
|
||||||
|
|
||||||
|
setImmediate(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
WebService.prototype.stop = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
setImmediate(function() {
|
||||||
|
if(self.server) {
|
||||||
|
self.server.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
WebService.prototype.setupAllRoutes = function() {
|
||||||
|
for(var key in this.node.services) {
|
||||||
|
this.node.services[key].setupRoutes(this.app);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
WebService.prototype.createMethodsMap = function() {
|
||||||
|
var self = this;
|
||||||
|
var methods = this.node.getAllAPIMethods();
|
||||||
|
this.methodsMap = {};
|
||||||
|
|
||||||
|
methods.forEach(function(data) {
|
||||||
|
var name = data[0];
|
||||||
|
var instance = data[1];
|
||||||
|
var method = data[2];
|
||||||
|
var args = data[3];
|
||||||
|
self.methodsMap[name] = {
|
||||||
|
fn: function() {
|
||||||
|
return method.apply(instance, arguments);
|
||||||
|
},
|
||||||
|
args: args
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
WebService.prototype.socketHandler = function(socket) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var bus = this.node.openBus();
|
||||||
|
|
||||||
|
socket.on('message', this.socketMessageHandler.bind(this));
|
||||||
|
|
||||||
|
socket.on('subscribe', function(name, params) {
|
||||||
|
bus.subscribe(name, params);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('unsubscribe', function(name, params) {
|
||||||
|
bus.unsubscribe(name, params);
|
||||||
|
});
|
||||||
|
|
||||||
|
var events = self.node.getAllPublishEvents();
|
||||||
|
|
||||||
|
events.forEach(function(event) {
|
||||||
|
bus.on(event.name, function() {
|
||||||
|
if(socket.connected) {
|
||||||
|
var results = [];
|
||||||
|
|
||||||
|
for(var i = 0; i < arguments.length; i++) {
|
||||||
|
results.push(arguments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var params = [event.name].concat(results);
|
||||||
|
socket.emit.apply(socket, params);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', function() {
|
||||||
|
bus.close();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
WebService.prototype.socketMessageHandler = function(message, socketCallback) {
|
||||||
|
if (this.methodsMap[message.method]) {
|
||||||
|
var params = message.params;
|
||||||
|
|
||||||
|
if(!params || !params.length) {
|
||||||
|
params = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(params.length !== this.methodsMap[message.method].args) {
|
||||||
|
return socketCallback({
|
||||||
|
error: {
|
||||||
|
message: 'Expected ' + this.methodsMap[message.method].args + ' parameter(s)'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var callback = function(err, result) {
|
||||||
|
var response = {};
|
||||||
|
if(err) {
|
||||||
|
response.error = {
|
||||||
|
message: err.toString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if(result) {
|
||||||
|
response.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
socketCallback(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
params = params.concat(callback);
|
||||||
|
this.methodsMap[message.method].fn.apply(this, params);
|
||||||
|
} else {
|
||||||
|
socketCallback({
|
||||||
|
error: {
|
||||||
|
message: 'Method Not Found'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = WebService;
|
||||||
@ -48,11 +48,13 @@
|
|||||||
"bindings": "^1.2.1",
|
"bindings": "^1.2.1",
|
||||||
"bitcore": "^0.13.0",
|
"bitcore": "^0.13.0",
|
||||||
"colors": "^1.1.2",
|
"colors": "^1.1.2",
|
||||||
|
"body-parser": "^1.13.3",
|
||||||
"commander": "^2.8.1",
|
"commander": "^2.8.1",
|
||||||
"errno": "^0.1.4",
|
"errno": "^0.1.4",
|
||||||
"leveldown": "^1.4.1",
|
"leveldown": "^1.4.1",
|
||||||
"levelup": "^1.2.1",
|
"levelup": "^1.2.1",
|
||||||
"liftoff": "^2.1.0",
|
"liftoff": "^2.1.0",
|
||||||
|
"express": "^4.13.3",
|
||||||
"memdown": "^1.0.0",
|
"memdown": "^1.0.0",
|
||||||
"mkdirp": "0.5.0",
|
"mkdirp": "0.5.0",
|
||||||
"nan": "1.3.0",
|
"nan": "1.3.0",
|
||||||
|
|||||||
@ -165,7 +165,7 @@ struct async_block_data {
|
|||||||
struct async_tx_data {
|
struct async_tx_data {
|
||||||
std::string err_msg;
|
std::string err_msg;
|
||||||
std::string txid;
|
std::string txid;
|
||||||
std::string blockhash;
|
std::string blockHash;
|
||||||
uint32_t nTime;
|
uint32_t nTime;
|
||||||
int64_t height;
|
int64_t height;
|
||||||
bool queryMempool;
|
bool queryMempool;
|
||||||
@ -1235,6 +1235,7 @@ async_get_tx_and_info(uv_work_t *req) {
|
|||||||
// Read header first to get block timestamp and hash
|
// Read header first to get block timestamp and hash
|
||||||
file >> blockHeader;
|
file >> blockHeader;
|
||||||
blockHash = blockHeader.GetHash();
|
blockHash = blockHeader.GetHash();
|
||||||
|
data->blockHash = blockHash.GetHex();
|
||||||
data->nTime = blockHeader.nTime;
|
data->nTime = blockHeader.nTime;
|
||||||
fseek(file.Get(), postx.nTxOffset, SEEK_CUR);
|
fseek(file.Get(), postx.nTxOffset, SEEK_CUR);
|
||||||
file >> ctx;
|
file >> ctx;
|
||||||
@ -1284,6 +1285,7 @@ async_get_tx_and_info_after(uv_work_t *req) {
|
|||||||
std::string stx = ssTx.str();
|
std::string stx = ssTx.str();
|
||||||
Local<Value> rawNodeBuffer = node::Buffer::New(isolate, stx.c_str(), stx.size());
|
Local<Value> rawNodeBuffer = node::Buffer::New(isolate, stx.c_str(), stx.size());
|
||||||
|
|
||||||
|
obj->Set(NanNew<String>("blockHash"), NanNew<String>(data->blockHash));
|
||||||
obj->Set(NanNew<String>("height"), NanNew<Number>(data->height));
|
obj->Set(NanNew<String>("height"), NanNew<Number>(data->height));
|
||||||
obj->Set(NanNew<String>("timestamp"), NanNew<Number>(data->nTime));
|
obj->Set(NanNew<String>("timestamp"), NanNew<Number>(data->nTime));
|
||||||
obj->Set(NanNew<String>("buffer"), rawNodeBuffer);
|
obj->Set(NanNew<String>("buffer"), rawNodeBuffer);
|
||||||
@ -1349,28 +1351,38 @@ NAN_METHOD(GetBlockIndex) {
|
|||||||
Isolate* isolate = Isolate::GetCurrent();
|
Isolate* isolate = Isolate::GetCurrent();
|
||||||
HandleScope scope(isolate);
|
HandleScope scope(isolate);
|
||||||
|
|
||||||
String::Utf8Value hash_(args[0]->ToString());
|
|
||||||
std::string hashStr = std::string(*hash_);
|
|
||||||
uint256 hash = uint256S(hashStr);
|
|
||||||
|
|
||||||
CBlockIndex* blockIndex;
|
CBlockIndex* blockIndex;
|
||||||
|
|
||||||
if (mapBlockIndex.count(hash) == 0) {
|
if (args[0]->IsNumber()) {
|
||||||
NanReturnValue(Undefined(isolate));
|
int64_t height = args[0]->IntegerValue();
|
||||||
|
blockIndex = chainActive[height];
|
||||||
|
|
||||||
|
if (blockIndex == NULL) {
|
||||||
|
NanReturnValue(Undefined(isolate));
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
blockIndex = mapBlockIndex[hash];
|
String::Utf8Value hash_(args[0]->ToString());
|
||||||
arith_uint256 cw = blockIndex->nChainWork;
|
std::string hashStr = std::string(*hash_);
|
||||||
CBlockIndex* prevBlockIndex = blockIndex->pprev;
|
uint256 hash = uint256S(hashStr);
|
||||||
const uint256* prevHash = prevBlockIndex->phashBlock;
|
if (mapBlockIndex.count(hash) == 0) {
|
||||||
|
NanReturnValue(Undefined(isolate));
|
||||||
Local<Object> obj = NanNew<Object>();
|
} else {
|
||||||
|
blockIndex = mapBlockIndex[hash];
|
||||||
obj->Set(NanNew<String>("chainWork"), NanNew<String>(cw.GetHex()));
|
}
|
||||||
obj->Set(NanNew<String>("prevHash"), NanNew<String>(prevHash->GetHex()));
|
|
||||||
|
|
||||||
NanReturnValue(obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arith_uint256 cw = blockIndex->nChainWork;
|
||||||
|
CBlockIndex* prevBlockIndex = blockIndex->pprev;
|
||||||
|
const uint256* prevHash = prevBlockIndex->phashBlock;
|
||||||
|
|
||||||
|
Local<Object> obj = NanNew<Object>();
|
||||||
|
obj->Set(NanNew<String>("hash"), NanNew<String>(blockIndex->phashBlock->GetHex()));
|
||||||
|
obj->Set(NanNew<String>("chainWork"), NanNew<String>(cw.GetHex()));
|
||||||
|
obj->Set(NanNew<String>("prevHash"), NanNew<String>(prevHash->GetHex()));
|
||||||
|
obj->Set(NanNew<String>("height"), NanNew<Number>(blockIndex->nHeight));
|
||||||
|
|
||||||
|
NanReturnValue(obj);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -183,19 +183,27 @@ describe('Bitcore Node', function() {
|
|||||||
node._unloadedServices = [
|
node._unloadedServices = [
|
||||||
{
|
{
|
||||||
name: 'chain',
|
name: 'chain',
|
||||||
dependencies: ['db']
|
module: {
|
||||||
|
dependencies: ['db']
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'db',
|
name: 'db',
|
||||||
|
module: {
|
||||||
dependencies: ['daemon', 'p2p']
|
dependencies: ['daemon', 'p2p']
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name:'daemon',
|
name:'daemon',
|
||||||
dependencies: []
|
module: {
|
||||||
|
dependencies: []
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'p2p',
|
name: 'p2p',
|
||||||
dependencies: []
|
module: {
|
||||||
|
dependencies: []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
var order = node.getServiceOrder();
|
var order = node.getServiceOrder();
|
||||||
@ -219,7 +227,8 @@ describe('Bitcore Node', function() {
|
|||||||
};
|
};
|
||||||
var service = {
|
var service = {
|
||||||
name: 'testservice',
|
name: 'testservice',
|
||||||
module: TestService
|
module: TestService,
|
||||||
|
config: {}
|
||||||
};
|
};
|
||||||
node._instantiateService(service);
|
node._instantiateService(service);
|
||||||
should.exist(node.services.testservice);
|
should.exist(node.services.testservice);
|
||||||
@ -254,11 +263,13 @@ describe('Bitcore Node', function() {
|
|||||||
node.getServiceOrder = sinon.stub().returns([
|
node.getServiceOrder = sinon.stub().returns([
|
||||||
{
|
{
|
||||||
name: 'test1',
|
name: 'test1',
|
||||||
module: TestService
|
module: TestService,
|
||||||
|
config: {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'test2',
|
name: 'test2',
|
||||||
module: TestService2
|
module: TestService2,
|
||||||
|
config: {}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
node.start(function() {
|
node.start(function() {
|
||||||
@ -295,11 +306,13 @@ describe('Bitcore Node', function() {
|
|||||||
node.getServiceOrder = sinon.stub().returns([
|
node.getServiceOrder = sinon.stub().returns([
|
||||||
{
|
{
|
||||||
name: 'test',
|
name: 'test',
|
||||||
module: TestService
|
module: TestService,
|
||||||
|
config: {}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'conflict',
|
name: 'conflict',
|
||||||
module: ConflictService
|
module: ConflictService,
|
||||||
|
config: {}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -9,13 +9,13 @@ describe('#start', function() {
|
|||||||
|
|
||||||
describe('will dynamically create a node from a configuration', function() {
|
describe('will dynamically create a node from a configuration', function() {
|
||||||
|
|
||||||
it('require each bitcore-node service', function(done) {
|
it('require each bitcore-node service with default config', function(done) {
|
||||||
var node;
|
var node;
|
||||||
var TestNode = function(options) {
|
var TestNode = function(options) {
|
||||||
options.services[0].should.deep.equal({
|
options.services[0].should.deep.equal({
|
||||||
name: 'address',
|
name: 'address',
|
||||||
module: AddressService,
|
module: AddressService,
|
||||||
dependencies: ['bitcoind', 'db']
|
config: {}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
TestNode.prototype.on = sinon.stub();
|
TestNode.prototype.on = sinon.stub();
|
||||||
@ -39,5 +39,43 @@ describe('#start', function() {
|
|||||||
node.should.be.instanceof(TestNode);
|
node.should.be.instanceof(TestNode);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('require each bitcore-node service with explicit config', function(done) {
|
||||||
|
var node;
|
||||||
|
var TestNode = function(options) {
|
||||||
|
options.services[0].should.deep.equal({
|
||||||
|
name: 'address',
|
||||||
|
module: AddressService,
|
||||||
|
config: {
|
||||||
|
param: 'test'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
TestNode.prototype.on = sinon.stub();
|
||||||
|
TestNode.prototype.chain = {
|
||||||
|
on: sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
var starttest = proxyquire('../../lib/scaffold/start', {
|
||||||
|
'../node': TestNode
|
||||||
|
});
|
||||||
|
|
||||||
|
node = starttest({
|
||||||
|
path: __dirname,
|
||||||
|
config: {
|
||||||
|
services: [
|
||||||
|
'address'
|
||||||
|
],
|
||||||
|
servicesConfig: {
|
||||||
|
'address': {
|
||||||
|
param: 'test'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
datadir: './data'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
node.should.be.instanceof(TestNode);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -398,6 +398,7 @@ describe('DB Service', function() {
|
|||||||
it('should give a transaction with height and timestamp', function(done) {
|
it('should give a transaction with height and timestamp', function(done) {
|
||||||
var txBuffer = new Buffer('01000000016f95980911e01c2c664b3e78299527a47933aac61a515930a8fe0213d1ac9abe01000000da0047304402200e71cda1f71e087c018759ba3427eb968a9ea0b1decd24147f91544629b17b4f0220555ee111ed0fc0f751ffebf097bdf40da0154466eb044e72b6b3dcd5f06807fa01483045022100c86d6c8b417bff6cc3bbf4854c16bba0aaca957e8f73e19f37216e2b06bb7bf802205a37be2f57a83a1b5a8cc511dc61466c11e9ba053c363302e7b99674be6a49fc0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9148a31d53a448c18996e81ce67811e5fb7da21e4468738c9d6f90000000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000', 'hex');
|
var txBuffer = new Buffer('01000000016f95980911e01c2c664b3e78299527a47933aac61a515930a8fe0213d1ac9abe01000000da0047304402200e71cda1f71e087c018759ba3427eb968a9ea0b1decd24147f91544629b17b4f0220555ee111ed0fc0f751ffebf097bdf40da0154466eb044e72b6b3dcd5f06807fa01483045022100c86d6c8b417bff6cc3bbf4854c16bba0aaca957e8f73e19f37216e2b06bb7bf802205a37be2f57a83a1b5a8cc511dc61466c11e9ba053c363302e7b99674be6a49fc0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9148a31d53a448c18996e81ce67811e5fb7da21e4468738c9d6f90000000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000', 'hex');
|
||||||
var info = {
|
var info = {
|
||||||
|
blockHash: '00000000000ec715852ea2ecae4dc8563f62d603c820f81ac284cd5be0a944d6',
|
||||||
height: 530482,
|
height: 530482,
|
||||||
timestamp: 1439559434000,
|
timestamp: 1439559434000,
|
||||||
buffer: txBuffer
|
buffer: txBuffer
|
||||||
@ -412,6 +413,7 @@ describe('DB Service', function() {
|
|||||||
|
|
||||||
db.getTransactionWithBlockInfo('2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f', true, function(err, tx) {
|
db.getTransactionWithBlockInfo('2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f', true, function(err, tx) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
|
tx.__blockHash.should.equal(info.blockHash);
|
||||||
tx.__height.should.equal(info.height);
|
tx.__height.should.equal(info.height);
|
||||||
tx.__timestamp.should.equal(info.timestamp);
|
tx.__timestamp.should.equal(info.timestamp);
|
||||||
done();
|
done();
|
||||||
|
|||||||
239
test/services/web.unit.js
Normal file
239
test/services/web.unit.js
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var should = require('chai').should();
|
||||||
|
var sinon = require('sinon');
|
||||||
|
var WebService = require('../../lib/services/web');
|
||||||
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
|
||||||
|
describe('WebService', function() {
|
||||||
|
var defaultNode = new EventEmitter();
|
||||||
|
|
||||||
|
describe('#start', function() {
|
||||||
|
it('should call the callback with no error', function(done) {
|
||||||
|
var web = new WebService({node: defaultNode});
|
||||||
|
web.start(function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#stop', function() {
|
||||||
|
it('should close the server if it exists', function(done) {
|
||||||
|
var web = new WebService({node: defaultNode});
|
||||||
|
web.server = {
|
||||||
|
close: sinon.spy()
|
||||||
|
};
|
||||||
|
|
||||||
|
web.stop(function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
web.server.close.callCount.should.equal(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#setupAllRoutes', function() {
|
||||||
|
it('should call setupRoutes on each module', function() {
|
||||||
|
var node = {
|
||||||
|
on: sinon.spy(),
|
||||||
|
services: {
|
||||||
|
one: {
|
||||||
|
setupRoutes: sinon.spy()
|
||||||
|
},
|
||||||
|
two: {
|
||||||
|
setupRoutes: sinon.spy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var web = new WebService({node: node});
|
||||||
|
|
||||||
|
web.setupAllRoutes();
|
||||||
|
node.services.one.setupRoutes.callCount.should.equal(1);
|
||||||
|
node.services.two.setupRoutes.callCount.should.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#createMethodsMap', function() {
|
||||||
|
it('should create the methodsMap correctly', function(done) {
|
||||||
|
var Module1 = function() {};
|
||||||
|
Module1.prototype.getAPIMethods = function() {
|
||||||
|
return [
|
||||||
|
['one', this, this.one, 1],
|
||||||
|
['two', this, this.two, 2]
|
||||||
|
];
|
||||||
|
};
|
||||||
|
Module1.prototype.one = function(param1, callback) {
|
||||||
|
callback(null, param1);
|
||||||
|
};
|
||||||
|
Module1.prototype.two = function(param1, param2, callback) {
|
||||||
|
callback(null, param1 + param2);
|
||||||
|
};
|
||||||
|
|
||||||
|
var module1 = new Module1();
|
||||||
|
|
||||||
|
var node = {
|
||||||
|
on: sinon.spy(),
|
||||||
|
getAllAPIMethods: sinon.stub().returns(module1.getAPIMethods())
|
||||||
|
};
|
||||||
|
|
||||||
|
var web = new WebService({node: node});
|
||||||
|
web.createMethodsMap();
|
||||||
|
|
||||||
|
Object.keys(web.methodsMap).length.should.equal(2);
|
||||||
|
web.methodsMap.one.args.should.equal(1);
|
||||||
|
web.methodsMap.two.args.should.equal(2);
|
||||||
|
web.methodsMap.one.fn(1, function(err, result) {
|
||||||
|
should.not.exist(err);
|
||||||
|
result.should.equal(1);
|
||||||
|
|
||||||
|
web.methodsMap.two.fn(1, 2, function(err, result) {
|
||||||
|
should.not.exist(err);
|
||||||
|
result.should.equal(3);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#socketHandler', function() {
|
||||||
|
var bus = new EventEmitter();
|
||||||
|
|
||||||
|
var Module1 = function() {};
|
||||||
|
Module1.prototype.getPublishEvents = function() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'event1'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
var module1 = new Module1();
|
||||||
|
var node = {
|
||||||
|
on: sinon.spy(),
|
||||||
|
openBus: sinon.stub().returns(bus),
|
||||||
|
getAllPublishEvents: sinon.stub().returns(module1.getPublishEvents())
|
||||||
|
};
|
||||||
|
|
||||||
|
var web;
|
||||||
|
var socket;
|
||||||
|
|
||||||
|
it('on message should call socketMessageHandler', function(done) {
|
||||||
|
web = new WebService({node: node});
|
||||||
|
web.socketMessageHandler = function(param1) {
|
||||||
|
param1.should.equal('data');
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
socket = new EventEmitter();
|
||||||
|
web.socketHandler(socket);
|
||||||
|
socket.emit('message', 'data');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('on subscribe should call bus.subscribe', function(done) {
|
||||||
|
bus.subscribe = function(param1) {
|
||||||
|
param1.should.equal('data');
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('subscribe', 'data');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('on unsubscribe should call bus.unsubscribe', function(done) {
|
||||||
|
bus.unsubscribe = function(param1) {
|
||||||
|
param1.should.equal('data');
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('unsubscribe', 'data');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('publish events from bus should be emitted from socket', function(done) {
|
||||||
|
socket.once('event1', function(param1, param2) {
|
||||||
|
param1.should.equal('param1');
|
||||||
|
param2.should.equal('param2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
socket.connected = true;
|
||||||
|
bus.emit('event1', 'param1', 'param2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('on disconnect should close bus', function(done) {
|
||||||
|
bus.close = function() {
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('disconnect');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#socketMessageHandler', function() {
|
||||||
|
var node = {
|
||||||
|
on: sinon.spy()
|
||||||
|
};
|
||||||
|
|
||||||
|
var web = new WebService({node: node});
|
||||||
|
web.methodsMap = {
|
||||||
|
one: {
|
||||||
|
fn: function(param1, param2, callback) {
|
||||||
|
var result = param1 + param2;
|
||||||
|
if(result > 0) {
|
||||||
|
return callback(null, result);
|
||||||
|
} else {
|
||||||
|
return callback(new Error('error'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: 2
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should give a Method Not Found error if method does not exist', function(done) {
|
||||||
|
var message = {
|
||||||
|
method: 'two',
|
||||||
|
params: [1, 2]
|
||||||
|
}
|
||||||
|
web.socketMessageHandler(message, function(response) {
|
||||||
|
should.exist(response.error);
|
||||||
|
response.error.message.should.equal('Method Not Found');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the method and return the result', function(done) {
|
||||||
|
var message = {
|
||||||
|
method: 'one',
|
||||||
|
params: [1, 2]
|
||||||
|
};
|
||||||
|
web.socketMessageHandler(message, function(response) {
|
||||||
|
should.not.exist(response.error);
|
||||||
|
response.result.should.equal(3);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should give an error if there is a param count mismatch', function(done) {
|
||||||
|
var message = {
|
||||||
|
method: 'one',
|
||||||
|
params: [1]
|
||||||
|
};
|
||||||
|
web.socketMessageHandler(message, function(response) {
|
||||||
|
should.exist(response.error);
|
||||||
|
response.error.message.should.equal('Expected 2 parameter(s)');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should give an error if the method gave an error', function(done) {
|
||||||
|
var message = {
|
||||||
|
method: 'one',
|
||||||
|
params: [-1, -2]
|
||||||
|
};
|
||||||
|
web.socketMessageHandler(message, function(response) {
|
||||||
|
should.exist(response.error);
|
||||||
|
response.error.message.should.equal('Error: error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user