diff --git a/lib/node.js b/lib/node.js index be6b8955..31953996 100644 --- a/lib/node.js +++ b/lib/node.js @@ -10,6 +10,7 @@ var index = require('./'); var log = index.log; var Bus = require('./bus'); var BaseService = require('./service'); +var WebService = require('./web'); function Node(config) { if(!(this instanceof Node)) { diff --git a/lib/scaffold/start.js b/lib/scaffold/start.js index 38fca447..093d987b 100644 --- a/lib/scaffold/start.js +++ b/lib/scaffold/start.js @@ -82,102 +82,7 @@ function start(options) { }); node.on('ready', function() { - - 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(); - }); - - }); - + log.info('Bitcore Node ready'); }); node.on('error', function(err) { @@ -207,7 +112,12 @@ function start(options) { if(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) { node.stop(function(err) { diff --git a/lib/service.js b/lib/service.js index 4407ff4a..97fed380 100644 --- a/lib/service.js +++ b/lib/service.js @@ -24,7 +24,9 @@ Service.dependencies = []; */ Service.prototype.blockHandler = function(block, add, callback) { // 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) { setImmediate(done); }; +/** + * Function to be called when bitcore-node is stopped + */ Service.prototype.stop = function(done) { setImmediate(done); }; +/** + * Setup express routes + * @param {Express} app + */ +Service.prototype.setupRoutes = function(app) { + // Setup express routes here +}; + module.exports = Service; diff --git a/lib/services/address.js b/lib/services/address.js index a254016b..f168a1d3 100644 --- a/lib/services/address.js +++ b/lib/services/address.js @@ -505,6 +505,7 @@ AddressService.prototype.getAddressHistoryForAddress = function(address, queryMe var confirmations = 0; if(transaction.__height >= 0) { confirmations = self.node.services.db.tip.__height - transaction.__height; + confirmations = self.node.services.db.tip.__height - transaction.__height + 1; } txinfos[transaction.hash] = { diff --git a/lib/services/db.js b/lib/services/db.js index 582b6088..ca864d13 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -217,6 +217,7 @@ DB.prototype.getTransactionWithBlockInfo = function(txid, queryMempool, callback } var tx = Transaction().fromBuffer(obj.buffer); + tx.__blockHash = obj.blockHash; tx.__height = obj.height; tx.__timestamp = obj.timestamp; @@ -657,6 +658,7 @@ DB.prototype.sync = function() { self.bitcoindSyncing = false; self.lastSavedMetadataThreshold = 0; + self.chain.saveMetadata(); // If bitcoind is completely synced if (self.node.services.bitcoind.isSynced()) { diff --git a/lib/web.js b/lib/web.js new file mode 100644 index 00000000..fd4730a8 --- /dev/null +++ b/lib/web.js @@ -0,0 +1,145 @@ +'use strict'; + +var http = require('http'); +var express = require('express'); +var bodyParser = require('body-parser'); +var socketio = require('socket.io'); + +var WebService = function(options) { + var self = this; + this.node = options.node; + this.port = options.port || 3456; + + this.node.on('ready', function() { + self.setupRoutes(); + self.server.listen(self.port); + }); +}; + +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)); + + 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 + }; + }); + + setImmediate(callback); +}; + +WebService.prototype.stop = function(callback) { + var self = this; + + setImmediate(function() { + if(self.server) { + self.server.close(); + } + + callback(); + }) +}; + +WebService.prototype.setupRoutes = function() { + for(var key in this.node.modules) { + this.node.modules[key].setupRoutes(this.app); + } +}; + +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 + ' 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); + this.methodsMap[message.method].fn.apply(this, params); + } else { + socketCallback({ + error: { + message: 'Method Not Found' + } + }); + } +}; + +module.exports = WebService; \ No newline at end of file diff --git a/package.json b/package.json index 3a0b4253..83c382a9 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,13 @@ "bindings": "^1.2.1", "bitcore": "^0.13.0", "colors": "^1.1.2", + "body-parser": "^1.13.3", "commander": "^2.8.1", "errno": "^0.1.4", "leveldown": "^1.4.1", "levelup": "^1.2.1", "liftoff": "^2.1.0", + "express": "^4.13.3", "memdown": "^1.0.0", "mkdirp": "0.5.0", "nan": "1.3.0", diff --git a/src/libbitcoind.cc b/src/libbitcoind.cc index 045c57e3..1c6fc879 100644 --- a/src/libbitcoind.cc +++ b/src/libbitcoind.cc @@ -165,7 +165,7 @@ struct async_block_data { struct async_tx_data { std::string err_msg; std::string txid; - std::string blockhash; + std::string blockHash; uint32_t nTime; int64_t height; bool queryMempool; @@ -1235,6 +1235,7 @@ async_get_tx_and_info(uv_work_t *req) { // Read header first to get block timestamp and hash file >> blockHeader; blockHash = blockHeader.GetHash(); + data->blockHash = blockHash.GetHex(); data->nTime = blockHeader.nTime; fseek(file.Get(), postx.nTxOffset, SEEK_CUR); file >> ctx; @@ -1284,6 +1285,7 @@ async_get_tx_and_info_after(uv_work_t *req) { std::string stx = ssTx.str(); Local rawNodeBuffer = node::Buffer::New(isolate, stx.c_str(), stx.size()); + obj->Set(NanNew("blockHash"), NanNew(data->blockHash)); obj->Set(NanNew("height"), NanNew(data->height)); obj->Set(NanNew("timestamp"), NanNew(data->nTime)); obj->Set(NanNew("buffer"), rawNodeBuffer); @@ -1349,28 +1351,38 @@ NAN_METHOD(GetBlockIndex) { Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); - String::Utf8Value hash_(args[0]->ToString()); - std::string hashStr = std::string(*hash_); - uint256 hash = uint256S(hashStr); - CBlockIndex* blockIndex; - if (mapBlockIndex.count(hash) == 0) { - NanReturnValue(Undefined(isolate)); + if (args[0]->IsNumber()) { + int64_t height = args[0]->IntegerValue(); + blockIndex = chainActive[height]; + + if (blockIndex == NULL) { + NanReturnValue(Undefined(isolate)); + } + } else { - blockIndex = mapBlockIndex[hash]; - arith_uint256 cw = blockIndex->nChainWork; - CBlockIndex* prevBlockIndex = blockIndex->pprev; - const uint256* prevHash = prevBlockIndex->phashBlock; - - Local obj = NanNew(); - - obj->Set(NanNew("chainWork"), NanNew(cw.GetHex())); - obj->Set(NanNew("prevHash"), NanNew(prevHash->GetHex())); - - NanReturnValue(obj); + String::Utf8Value hash_(args[0]->ToString()); + std::string hashStr = std::string(*hash_); + uint256 hash = uint256S(hashStr); + if (mapBlockIndex.count(hash) == 0) { + NanReturnValue(Undefined(isolate)); + } else { + blockIndex = mapBlockIndex[hash]; + } } + arith_uint256 cw = blockIndex->nChainWork; + CBlockIndex* prevBlockIndex = blockIndex->pprev; + const uint256* prevHash = prevBlockIndex->phashBlock; + + Local obj = NanNew(); + obj->Set(NanNew("hash"), NanNew(blockIndex->phashBlock->GetHex())); + obj->Set(NanNew("chainWork"), NanNew(cw.GetHex())); + obj->Set(NanNew("prevHash"), NanNew(prevHash->GetHex())); + obj->Set(NanNew("height"), NanNew(blockIndex->nHeight)); + + NanReturnValue(obj); }; /** diff --git a/test/services/db.unit.js b/test/services/db.unit.js index 340a79ca..4bfd2388 100644 --- a/test/services/db.unit.js +++ b/test/services/db.unit.js @@ -398,6 +398,7 @@ describe('DB Service', function() { it('should give a transaction with height and timestamp', function(done) { var txBuffer = new Buffer('01000000016f95980911e01c2c664b3e78299527a47933aac61a515930a8fe0213d1ac9abe01000000da0047304402200e71cda1f71e087c018759ba3427eb968a9ea0b1decd24147f91544629b17b4f0220555ee111ed0fc0f751ffebf097bdf40da0154466eb044e72b6b3dcd5f06807fa01483045022100c86d6c8b417bff6cc3bbf4854c16bba0aaca957e8f73e19f37216e2b06bb7bf802205a37be2f57a83a1b5a8cc511dc61466c11e9ba053c363302e7b99674be6a49fc0147522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452aeffffffff0240420f000000000017a9148a31d53a448c18996e81ce67811e5fb7da21e4468738c9d6f90000000017a9148ce5408cfeaddb7ccb2545ded41ef478109454848700000000', 'hex'); var info = { + blockHash: '00000000000ec715852ea2ecae4dc8563f62d603c820f81ac284cd5be0a944d6', height: 530482, timestamp: 1439559434000, buffer: txBuffer @@ -412,6 +413,7 @@ describe('DB Service', function() { db.getTransactionWithBlockInfo('2d950d00494caf6bfc5fff2a3f839f0eb50f663ae85ce092bc5f9d45296ae91f', true, function(err, tx) { should.not.exist(err); + tx.__blockHash.should.equal(info.blockHash); tx.__height.should.equal(info.height); tx.__timestamp.should.equal(info.timestamp); done();