UNPKG

insight-via-api

Version:

A Viacoin blockchain REST and web socket API service for Viacore Node.

296 lines (241 loc) 10.3 kB
'use strict'; var Writable = require('stream').Writable; var bodyParser = require('body-parser'); var compression = require('compression'); var BaseService = require('./service'); var inherits = require('util').inherits; var BlockController = require('./blocks'); var TxController = require('./transactions'); var AddressController = require('./addresses'); var StatusController = require('./status'); var MessagesController = require('./messages'); var UtilsController = require('./utils'); var CurrencyController = require('./currency'); var RateLimiter = require('./ratelimiter'); var morgan = require('morgan'); var bitcore = require('viacore-lib'); var _ = bitcore.deps._; var $ = bitcore.util.preconditions; var Transaction = bitcore.Transaction; var EventEmitter = require('events').EventEmitter; /** * A service for Bitcore to enable HTTP routes to query information about the blockchain. * * @param {Object} options * @param {Boolean} options.enableCache - This will enable cache-control headers * @param {Number} options.cacheShortSeconds - The time to cache short lived cache responses. * @param {Number} options.cacheLongSeconds - The time to cache long lived cache responses. * @param {String} options.routePrefix - The URL route prefix */ var InsightAPI = function(options) { BaseService.call(this, options); // in minutes this.currencyRefresh = options.currencyRefresh || CurrencyController.DEFAULT_CURRENCY_DELAY; this.subscriptions = { inv: [] }; if (!_.isUndefined(options.enableCache)) { $.checkArgument(_.isBoolean(options.enableCache)); this.enableCache = options.enableCache; } this.cacheShortSeconds = options.cacheShortSeconds; this.cacheLongSeconds = options.cacheLongSeconds; this.rateLimiterOptions = options.rateLimiterOptions; this.disableRateLimiter = options.disableRateLimiter; this.blockSummaryCacheSize = options.blockSummaryCacheSize || BlockController.DEFAULT_BLOCKSUMMARY_CACHE_SIZE; this.blockCacheSize = options.blockCacheSize || BlockController.DEFAULT_BLOCK_CACHE_SIZE; if (!_.isUndefined(options.routePrefix)) { this.routePrefix = options.routePrefix; } else { this.routePrefix = this.name; } this.txController = new TxController(this.node); }; InsightAPI.dependencies = ['bitcoind', 'web']; inherits(InsightAPI, BaseService); InsightAPI.prototype.cache = function(maxAge) { var self = this; return function(req, res, next) { if (self.enableCache) { res.header('Cache-Control', 'public, max-age=' + maxAge); } next(); }; }; InsightAPI.prototype.cacheShort = function() { var seconds = this.cacheShortSeconds || 30; // thirty seconds return this.cache(seconds); }; InsightAPI.prototype.cacheLong = function() { var seconds = this.cacheLongSeconds || 86400; // one day return this.cache(seconds); }; InsightAPI.prototype.getRoutePrefix = function() { return this.routePrefix; }; InsightAPI.prototype.start = function(callback) { this.node.services.bitcoind.on('tx', this.transactionEventHandler.bind(this)); this.node.services.bitcoind.on('block', this.blockEventHandler.bind(this)); setImmediate(callback); }; InsightAPI.prototype.createLogInfoStream = function() { var self = this; function Log(options) { Writable.call(this, options); } inherits(Log, Writable); Log.prototype._write = function (chunk, enc, callback) { self.node.log.info(chunk.slice(0, chunk.length - 1)); // remove new line and pass to logger callback(); }; var stream = new Log(); return stream; }; InsightAPI.prototype.getRemoteAddress = function(req) { if (req.headers['cf-connecting-ip']) { return req.headers['cf-connecting-ip']; } return req.socket.remoteAddress; }; InsightAPI.prototype._getRateLimiter = function() { var rateLimiterOptions = _.isUndefined(this.rateLimiterOptions) ? {} : _.clone(this.rateLimiterOptions); rateLimiterOptions.node = this.node; var limiter = new RateLimiter(rateLimiterOptions); return limiter; }; InsightAPI.prototype.setupRoutes = function(app) { var self = this; //Enable rate limiter if (!this.disableRateLimiter) { var limiter = this._getRateLimiter(); app.use(limiter.middleware()); } //Setup logging morgan.token('remote-forward-addr', function(req){ return self.getRemoteAddress(req); }); var logFormat = ':remote-forward-addr ":method :url" :status :res[content-length] :response-time ":user-agent" '; var logStream = this.createLogInfoStream(); app.use(morgan(logFormat, {stream: logStream})); //Enable compression app.use(compression()); //Enable urlencoded data app.use(bodyParser.urlencoded({extended: true})); //Enable CORS app.use(function(req, res, next) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, HEAD, PUT, POST, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Content-Length, Cache-Control, cf-connecting-ip'); var method = req.method && req.method.toUpperCase && req.method.toUpperCase(); if (method === 'OPTIONS') { res.statusCode = 204; res.end(); } else { next(); } }); //Block routes var blockOptions = { node: this.node, blockSummaryCacheSize: this.blockSummaryCacheSize, blockCacheSize: this.blockCacheSize }; var blocks = new BlockController(blockOptions); app.get('/blocks', this.cacheShort(), blocks.list.bind(blocks)); app.get('/block/:blockHash', this.cacheShort(), blocks.checkBlockHash.bind(blocks), blocks.show.bind(blocks)); app.param('blockHash', blocks.block.bind(blocks)); app.get('/rawblock/:blockHash', this.cacheLong(), blocks.checkBlockHash.bind(blocks), blocks.showRaw.bind(blocks)); app.param('blockHash', blocks.rawBlock.bind(blocks)); app.get('/block-index/:height', this.cacheShort(), blocks.blockIndex.bind(blocks)); app.param('height', blocks.blockIndex.bind(blocks)); // Transaction routes var transactions = new TxController(this.node); app.get('/tx/:txid', this.cacheShort(), transactions.show.bind(transactions)); app.param('txid', transactions.transaction.bind(transactions)); app.get('/txs', this.cacheShort(), transactions.list.bind(transactions)); app.post('/tx/send', transactions.send.bind(transactions)); // Raw Routes app.get('/rawtx/:txid', this.cacheLong(), transactions.showRaw.bind(transactions)); app.param('txid', transactions.rawTransaction.bind(transactions)); // Address routes var addresses = new AddressController(this.node); app.get('/addr/:addr', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.show.bind(addresses)); app.get('/addr/:addr/utxo', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.utxo.bind(addresses)); app.get('/addrs/:addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); app.post('/addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); app.get('/addrs/:addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); app.post('/addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); // Address property routes app.get('/addr/:addr/balance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.balance.bind(addresses)); app.get('/addr/:addr/totalReceived', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalReceived.bind(addresses)); app.get('/addr/:addr/totalSent', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalSent.bind(addresses)); app.get('/addr/:addr/unconfirmedBalance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.unconfirmedBalance.bind(addresses)); // Status route var status = new StatusController(this.node); app.get('/status', this.cacheShort(), status.show.bind(status)); app.get('/sync', this.cacheShort(), status.sync.bind(status)); app.get('/peer', this.cacheShort(), status.peer.bind(status)); app.get('/version', this.cacheShort(), status.version.bind(status)); // Address routes var messages = new MessagesController(this.node); app.get('/messages/verify', messages.verify.bind(messages)); app.post('/messages/verify', messages.verify.bind(messages)); // Utils route var utils = new UtilsController(this.node); app.get('/utils/estimatefee', utils.estimateFee.bind(utils)); // Currency var currency = new CurrencyController({ node: this.node, currencyRefresh: this.currencyRefresh }); app.get('/currency', currency.index.bind(currency)); // Not Found app.use(function(req, res) { res.status(404).jsonp({ status: 404, url: req.originalUrl, error: 'Not found' }); }); }; InsightAPI.prototype.getPublishEvents = function() { return [ { name: 'inv', scope: this, subscribe: this.subscribe.bind(this), unsubscribe: this.unsubscribe.bind(this), extraEvents: ['tx', 'block'] } ]; }; InsightAPI.prototype.blockEventHandler = function(hashBuffer) { // Notify inv subscribers for (var i = 0; i < this.subscriptions.inv.length; i++) { this.subscriptions.inv[i].emit('block', hashBuffer.toString('hex')); } }; InsightAPI.prototype.transactionEventHandler = function(txBuffer) { var tx = new Transaction().fromBuffer(txBuffer); var result = this.txController.transformInvTransaction(tx); for (var i = 0; i < this.subscriptions.inv.length; i++) { this.subscriptions.inv[i].emit('tx', result); } }; InsightAPI.prototype.subscribe = function(emitter) { $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); var emitters = this.subscriptions.inv; var index = emitters.indexOf(emitter); if(index === -1) { emitters.push(emitter); } }; InsightAPI.prototype.unsubscribe = function(emitter) { $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); var emitters = this.subscriptions.inv; var index = emitters.indexOf(emitter); if(index > -1) { emitters.splice(index, 1); } }; module.exports = InsightAPI;