Merge branch 'next' of https://github.com/bitpay/insight into next

This commit is contained in:
Darren Nelsen 2017-08-22 10:08:50 -04:00
commit c6b0e66a42
33 changed files with 912 additions and 546 deletions

View File

@ -4,14 +4,14 @@
## Requirements ## Requirements
Insight requires [Node.js](https://nodejs.org) and [MongoDB](https://www.mongodb.com/). Consider using [n](https://github.com/tj/n) and [m](https://github.com/aheckmann/m) to install the latest versions. Insight requires [Node.js](https://nodejs.org) 8.2 and [MongoDB](https://www.mongodb.com/). Consider using [n](https://github.com/tj/n) and [m](https://github.com/aheckmann/m) to install the latest versions.
## Quick Start ## Quick Start
To get started, clone this repository, then with `mongod` running install and run insight: To get started, clone this repository, then with `mongod` running install and run insight:
```bash ```bash
git clone -b next https://github.com/bitpay/insight.git && cd insight git clone -b next https://github.com/bitpay/insight.git && cd insight/server
npm install npm install
npm start npm start
``` ```

View File

@ -8,6 +8,9 @@
"no-multi-spaces": 0, "no-multi-spaces": 0,
"no-use-before-define": 1, "no-use-before-define": 1,
"object-shorthand": 1, "object-shorthand": 1,
"key-spacing": 0 "key-spacing": 0,
"no-plusplus": 0,
"no-unused-vars": 1,
"no-param-reassign": 1
} }
} }

View File

@ -1,15 +1,19 @@
const config = { const config = {
start_node: true, start_node: true,
logging: 'debug', logging: 'debug',
bcoin_http: 'localhost',
bcoin: { bcoin: {
network: 'main', network: 'main',
db: 'leveldb', db: 'leveldb',
prefix: '.', prefix: '.',
checkpoints: true, checkpoints: true,
workers: true, workers: false,
logLevel: 'info', logLevel: 'info',
'max-inbound': 10, 'max-inbound': 10,
'max-outbound': 10, 'max-outbound': 10,
'index-tx': true,
'index-address': true,
'http-port': 8332,
}, },
mongodb: { mongodb: {
uri: 'mongodb://localhost/bitcore', uri: 'mongodb://localhost/bitcore',
@ -22,6 +26,11 @@ const config = {
json_spaces: 2, json_spaces: 2,
currency_refresh: 60, currency_refresh: 60,
ticker_url: 'https://www.bitstamp.net/api/ticker/', ticker_url: 'https://www.bitstamp.net/api/ticker/',
ticker_prop: 'bitstamp',
max_blocks: 72,
max_txs: 50,
max_page_txs: 10,
request_ttl: 100000,
}, },
}; };

View File

@ -1,7 +1,7 @@
const Bcoin = require('./lib/node'); const Bcoin = require('./lib/node');
const config = require('./config'); const config = require('./config');
const logger = require('./lib/logger'); const logger = require('./lib/logger');
const Api = require('./lib/api').server; const Api = require('./lib/api');
const db = require('./lib/db'); const db = require('./lib/db');
logger.log('debug', logger.log('debug',
@ -10,7 +10,6 @@ logger.log('debug',
db.connect(config.mongodb.uri, config.mongodb.options); db.connect(config.mongodb.uri, config.mongodb.options);
db.connection.once('open', () => { db.connection.once('open', () => {
if (config.start_node) Bcoin.start(); if (config.start_node) Bcoin.start();
Api.listen(config.api.port, () => { Api.listen(config.api.port, () => {

View File

@ -1,46 +1,108 @@
const Block = require('../../models/block.js'); const logger = require('../logger');
const logger = require('../logger'); const request = require('request');
const config = require('../../config');
const util = require('../util');
const MAX_BLOCKS = 200; const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`;
const TTL = config.api.request_ttl;
module.exports = function AddressAPI(router) { module.exports = function AddressAPI(router) {
router.get('/addr/:addr', (req, res) => { router.get('/addr/:addr', (req, res) => {
res.send(req.params.addr); const addr = req.params.addr || '';
if (!util.isBitcoinAddress(addr)) {
return res.status(400).send({
error: 'Invalid bitcoin address',
});
}
logger.log('debug',
'Warning: Requesting data from Bcoin by address, may take some time');
// Get Bcoin data
return request(`${API_URL}/tx/address/${addr}`,
{ timeout: TTL },
(error, bcoinRes, bcoinTxs) => {
if (error) {
logger.log('error',
`${error}`);
return res.status(404).send({});
}
let txs = {};
try {
txs = JSON.parse(bcoinTxs);
} catch (e) {
logger.log('error',
`${e}`);
return res.status(404).send({});
}
// Sum the matching outputs for every tx
const totalReceived = txs.reduce((total, tx) => total + tx.outputs.reduce((sum, output) => {
if (output.address === req.params.addr) {
return sum + output.value;
}
return sum;
}, 0), 0) || 0;
// Sum the matching inputs for every tx
const totalSpent = txs.reduce((total, tx) => total + tx.inputs.reduce((sum, input) => {
if (input.coin && input.coin.address === req.params.addr) {
return sum + input.coin.value;
}
return sum;
}, 0), 0) || 0;
// Match Insight API
return res.json({
addrStr: req.params.addr,
balance: (totalReceived - totalSpent) / 1e8,
balanceSat: totalReceived - totalSpent,
totalReceived: totalReceived / 1e8,
totalReceivedSat: totalReceived,
totalSent: totalSpent / 1e8,
totalSentSat: totalSpent,
unconfirmedBalance: 0,
unconfirmedBalanceSat: 0,
unconfirmedTxApperances: 0,
txApperances: txs.length,
});
});
}); });
// Stubbed by # to help with tasking
router.get('/addr/:addr/utxo', (req, res) => { router.get('/addr/:addr/utxo', (req, res) => {
res.send(req.params.addr); res.send('1');
}); });
router.get('/addr/:addr/balance', (req, res) => { router.get('/addr/:addr/balance', (req, res) => {
res.send(req.params.addr); res.send('2');
}); });
router.get('/addr/:addr/totalReceived', (req, res) => { router.get('/addr/:addr/totalReceived', (req, res) => {
res.send(req.params.addr); res.send('3');
}); });
router.get('/addr/:addr/totalSent', (req, res) => { router.get('/addr/:addr/totalSent', (req, res) => {
res.send(req.params.addr); res.send('4');
}); });
router.get('/addr/:addr/unconfirmedBalance', (req, res) => { router.get('/addr/:addr/unconfirmedBalance', (req, res) => {
res.send(req.params.addr); res.send('5');
}); });
router.get('/addrs/:addrs/utxo', (req, res) => { router.get('/addrs/:addrs/utxo', (req, res) => {
res.send(req.params.addrs); res.send('6');
}); });
router.post('/addrs/utxo', (req, res) => { router.post('/addrs/utxo', (req, res) => {
res.send('post stub'); res.send('7');
}); });
router.get('/addrs/:addrs/txs', (req, res) => { router.get('/addrs/:addrs/txs', (req, res) => {
res.send(req.params.addrs); res.send('8');
}); });
router.post('/addrs/txs', (req, res) => { router.post('/addrs/txs', (req, res) => {
res.send('post stub'); res.send('9');
}); });
}; };

View File

@ -1,67 +1,54 @@
const Block = require('../../models/block.js');
const logger = require('../logger'); const logger = require('../logger');
const db = require('../db');
const MAX_BLOCKS = 200; const util = require('../util');
function getBlock(params, options, limit, cb) {
const defaultOptions = { _id: 0 };
if (!Number.isInteger(limit)) {
limit = MAX_BLOCKS;
}
Object.assign(defaultOptions, options);
Block.find(
params,
defaultOptions,
cb)
.sort({ height: -1 })
.limit(limit);
}
module.exports = function BlockAPI(router) { module.exports = function BlockAPI(router) {
router.get('/block/:blockHash', (req, res) => { router.get('/block/:blockHash', (req, res) => {
getBlock( const blockHash = req.params.blockHash;
{ hash: req.params.blockHash },
if (!util.isBlockHash(blockHash)) {
return res.status(400).send({
error: 'Invalid bitcoin address',
});
}
// Pass Mongo params, fields and limit to db api.
db.blocks.getBlock(
{ hash: blockHash },
{ rawBlock: 0 }, { rawBlock: 0 },
MAX_BLOCKS, 1,
(err, block) => { (err, block) => {
if (err) { if (err) {
res.status(501).send();
logger.log('err', err); logger.log('err', err);
return res.status(404).send();
} }
if (block[0]) { // Format the request for insight ui
const b = block[0]; return res.json({
res.json({ hash: block.hash,
hash: b.hash, size: block.size,
size: b.size, height: block.height,
height: b.height, version: block.version,
version: b.version, merkleroot: block.merkleRoot,
merkleroot: b.merkleRoot, tx: block.txs,
tx: b.txs, time: block.ts,
time: b.ts, nonce: block.nonce,
nonce: b.nonce, bits: block.bits.toString(16),
bits: b.bits.toString(16), difficulty: 1,
difficulty: 1, chainwork: block.chainwork.toString(16),
chainwork: b.chainwork.toString(16), confirmations: 0,
confirmations: 0, previousblockhash: block.prevBlock,
previousblockhash: b.prevBlock, nextblockhash: 0,
nextblockhash: 0, reward: block.reward / 1e8,
reward: b.reward / 1e8, isMainChain: true,
isMainChain: true, poolInfo: {},
poolInfo: {}, });
});
} else {
res.send();
}
}); });
}); });
router.get('/blocks', (req, res) => { router.get('/blocks', (req, res) => {
const limit = parseInt(req.query.limit) || MAX_BLOCKS; const limit = parseInt(req.query.limit, 10) || 100;
// Pass Mongo params, fields and limit to db api.
getBlock( db.blocks.getBlocks(
{}, {},
{ height: 1, { height: 1,
size: 1, size: 1,
@ -73,56 +60,66 @@ module.exports = function BlockAPI(router) {
limit, limit,
(err, blocks) => { (err, blocks) => {
if (err) { if (err) {
res.status(501).send(); logger.log('err',
logger.log('err', err); `/blocks: ${err}`);
return res.status(404).send();
} }
res.json({ // Format the request for insight ui
return res.json({
blocks: blocks.map(block => ({ blocks: blocks.map(block => ({
hash: block.hash, hash: block.hash,
height: block.height, height: block.height,
size: block.size, size: block.size,
time: block.ts, time: block.ts,
txlength: block.txs.length, txlength: block.txs.length,
poolInfo: {}, poolInfo: {},
})), })),
length: blocks.length, length: blocks.length,
pagination: {}, pagination: {},
}); });
}); });
}); });
router.get('/rawblock/:blockHash', (req, res) => { router.get('/rawblock/:blockHash', (req, res) => {
getBlock( const blockHash = req.params.blockHash || '';
{ hash: req.params.blockHash },
if (!util.isBlockHash(blockHash)) {
return res.status(400).send({
error: 'Invalid bitcoin address',
});
}
// Pass Mongo params, fields and limit to db api.
db.blocks.getBlock(
{ hash: blockHash },
{ rawBlock: 1 }, { rawBlock: 1 },
MAX_BLOCKS, 1,
(err, block) => { (err, block) => {
if (err) { if (err) {
res.status(501).send(); logger.log('err',
logger.log('err', err); `/rawblock/:blockHash: ${err}`);
return res.status(404).send();
} }
res.json(block[0]); return res.json(block);
}); });
}); });
router.get('/block-index/:height', (req, res) => { router.get('/block-index/:height', (req, res) => {
getBlock( const blockHeight = parseInt(req.params.height, 10) || 1;
{ height: req.params.height }, // Pass Mongo params, fields and limit to db api.
db.blocks.getBlock(
{ height: blockHeight },
{ hash: 1 }, { hash: 1 },
MAX_BLOCKS, 1,
(err, block) => { (err, block) => {
if (err) { if (err) {
res.status(501).send(); logger.log('err',
logger.log('err', err); `/block-index/:height: ${err}`);
} return res.status(404).send();
if (block[0]) {
res.json({
blockHash: block[0].hash,
});
} else {
res.send();
} }
return res.json({
blockHash: block.hash,
});
}); });
}); });
}; };

View File

@ -1,5 +1,5 @@
module.exports = function (req, res, next) { module.exports = function (req, res, next) {
let allowed = { const allowed = {
origins: [ origins: [
'*', '*',

View File

@ -2,17 +2,25 @@ const config = require('../../config');
const logger = require('../logger'); const logger = require('../logger');
const request = require('request'); const request = require('request');
// Retrieve the configured endpoint's ticker rate at a
// set interval
const refreshInterval = config.api.currency_refresh >= 1 ? const refreshInterval = config.api.currency_refresh >= 1 ?
config.api.currency_refresh * 1000 : config.api.currency_refresh * 1000 :
60 * 1000; 60 * 1000;
let lastRate = 0; let lastRate = 0;
getRate(); init();
setInterval(() => { function init() {
getRate(); getRate();
}, refreshInterval);
setInterval(() => {
getRate();
}, refreshInterval);
}
// Make the request to the remote API
function getRate() { function getRate() {
request(config.api.ticker_url, (err, res, body) => { request(config.api.ticker_url, (err, res, body) => {
if (err) { if (err) {
@ -32,11 +40,13 @@ function getRate() {
} }
module.exports = function currencyAPI(app) { module.exports = function currencyAPI(app) {
// Return the ticker price
app.get('/currency', (req, res) => { app.get('/currency', (req, res) => {
const data = {};
data[config.api.ticker_prop] = lastRate;
res.json({ res.json({
data: { data,
bitstamp: lastRate,
},
}); });
}); });
}; };

View File

@ -1,19 +1,28 @@
const express = require('express'); const express = require('express');
const config = require('../../config'); const config = require('../../config');
const bodyParser = require('body-parser');
const app = express(); const app = express();
const api = express.Router(); const api = express.Router();
const cors = require('./cors'); const cors = require('./cors');
app.use(cors); app.use(cors);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Serve insight ui front end from root dir public folder // Serve insight ui front end from root dir public folder
app.use('/', express.static('./public')); app.use(express.static('./public'));
app.use('/:stuff', express.static('./public'));
app.use('/blocks', express.static('./public')); app.use('/blocks', express.static('./public'));
app.use('/blocks/:blockhash', express.static('./public'));
app.use('/block-index', express.static('./public'));
app.use('/block-index/:height', express.static('./public'));
app.use('/blocks-date/:date', express.static('./public')); app.use('/blocks-date/:date', express.static('./public'));
app.use('/block/:blockhash', express.static('./public')); app.use('/block/:blockhash', express.static('./public'));
app.use('/tx/:txid', express.static('./public')); app.use('/tx/:txid', express.static('./public'));
app.use('/address/:addr', express.static('./public')); app.use('/address/:addr', express.static('./public'));
app.use('/status', express.static('./public'));
app.use('/status/:stuff', express.static('./public'));
app.set('json spaces', config.api.json_spaces); app.set('json spaces', config.api.json_spaces);
@ -36,12 +45,7 @@ app.use((req, res) => {
}); });
}); });
// Socket server
const server = require('http').Server(app); const server = require('http').Server(app);
const io = require('socket.io')(server);
const SocketAPI = require('./socket')(io); module.exports = server;
module.exports = {
server,
io,
};

View File

@ -1,10 +1,42 @@
const Message = require('bitcore-message');
const util = require('../util');
// Copied from previous source
function verifyMessage(req, res) {
const address = req.body.address || req.query.address;
const signature = req.body.signature || req.query.signature;
const message = req.body.message || req.query.message;
if (!util.isBitcoinAddress(address)) {
return res.status(400).send({
error: 'Invalid bitcoin address',
});
}
if (!address || !signature || !message) {
return res.json({
message: 'Missing parameters (expected "address", "signature" and "message")',
code: 1,
});
}
let valid;
try {
valid = new Message(message).verify(address, signature);
} catch (err) {
return res.json({
message: `Unexpected error: ${err.message}`,
code: 1,
});
}
res.json({ result: valid });
}
module.exports = function messageAPI(router) { module.exports = function messageAPI(router) {
router.get('/messages/verify', (req, res) => { router.get('/messages/verify', (req, res) => {
res.send('messages verify'); verifyMessage(req, res);
}); });
router.post('/messages/verify', (req, res) => { router.post('/messages/verify', (req, res) => {
res.send('post messages verify'); verifyMessage(req, res);
}); });
router.get('/utils/estimatefee', (req, res) => { router.get('/utils/estimatefee', (req, res) => {

View File

@ -1,15 +1,68 @@
module.exports = function addressrouter(io) { const server = require('.');
io.on('connection', (socket) => { const io = require('socket.io')(server);
socket.on('subscribe', (data) => {
});
socket.on('message', (data) => { let refreshBlocks = false;
}); const txInterval = 200;
let txCounter = 0;
socket.on('unsubscribe', (data) => { // Not quite debouncing
}); setInterval(() => {
refreshBlocks = true;
}, 10000);
socket.on('disconnect', (data) => {
}); io.on('connection', (socket) => {
socket.on('subscribe', (data) => {
}); });
socket.on('message', (data) => {
});
socket.on('unsubscribe', (data) => {
});
socket.on('disconnect', (data) => {
});
});
// Emit block refresh and txs
function processBlock(entry, block) {
if (refreshBlocks) {
refreshBlocks = false;
emitBlock(entry);
}
block.txs.forEach((tx) => {
txCounter++;
if (txCounter % txInterval === 0) {
txCounter = 0;
emitTx(tx);
}
});
}
function emitBlock(block) {
io.sockets.emit('block', {
hash: block.toJSON().hash,
});
}
function emitTx(transaction) {
const txJSON = transaction.toJSON();
io.sockets.emit('tx', {
txid: txJSON.hash,
valueOut: transaction.outputs.reduce((sum, output) => {
output = output.toJSON();
const valB = (output.value || output.valueOut.value || 0) / 1e8;
return sum + valB;
}, 0),
});
}
module.exports = {
io,
processBlock,
emitBlock,
emitTx,
}; };

View File

@ -1,58 +1,104 @@
const Block = require('../../models/block.js'); const request = require('request');
const pkg = require('../../package.json'); const pkg = require('../../package.json');
const config = require('../../config');
const netCfg = require('bcoin/lib/net/common');
const logger = require('../logger'); const logger = require('../logger');
const db = require('../db');
const MAX_BLOCKS = 200; const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}/`;
// Not dry, in multiple APIs. Refactor to db api // Retrieve Bcoin status
function getBlock(params, options, limit, cb) { function getStatus(cb) {
const defaultOptions = { _id: 0 }; request(`${API_URL}`, (err, localRes, status) => {
if (err) {
if (!Number.isInteger(limit)) { logger.log('error',
limit = MAX_BLOCKS; `getStatus ${err}`);
} return cb(err);
}
Object.assign(defaultOptions, options); try {
status = JSON.parse(status);
Block.find( } catch (e) {
params, logger.log('error',
defaultOptions, `getStatus JSON.parse: ${e}`);
cb) return cb(e);
.sort({ height: -1 }) }
.limit(limit); return cb(null, status);
});
} }
// UI assigns Multiple Responsibilities depending on params
module.exports = function statusAPI(router) { module.exports = function statusAPI(router) {
// Get last block hash or node status
router.get('/status', (req, res) => { router.get('/status', (req, res) => {
res.json({ if (req.query.q === 'getLastBlockHash') {
info: { db.blocks.getBlock(
version: 0, {},
protocolversion: 0, { hash: 1 },
blocks: 0, 1,
timeoffset: 0, (err, block) => {
connections: 0, if (err) {
proxy: '', logger.log('error',
difficulty: 0, `${err}`);
testnet: false, return res.status(404).send(err);
relayfee: 0, }
errors: '', return res.send({
network: 'main', syncTipHash: block.hash,
}, lastblockhash: block.hash,
}); });
});
} else {
getStatus((err, status) => {
if (err) {
logger.log('err',
`/status getStatus: ${err}`);
return res.status(404).send(err);
}
if (!status) {
logger.log('err',
'/status getStatus: no Status');
return res.status(404).send();
}
return res.json({
info: {
version: status.version,
protocolversion: netCfg.PROTOCOL_VERSION,
blocks: status.chain.height,
timeoffset: status.time.offset,
connections: status.pool.outbound,
proxy: '',
difficulty: 0,
testnet: status.network !== 'main',
relayfee: 0,
errors: '',
network: status.network,
},
});
});
}
}); });
// Get Bcoin sync status
router.get('/sync', (req, res) => { router.get('/sync', (req, res) => {
res.json({ getStatus((err, status) => {
status: '', if (err) {
blockChainHeight: 0, logger.log('err',
syncPercentage: 0, `/sync: ${err}`);
height: 0, return res.status(404).send(err);
error: null, }
type: 'bitcore node', if (!status) {
logger.log('err',
'/sync: no status');
return res.status(404).send();
}
return res.json({
status: status.chain.progress === 100 ? 'synced' : 'syncing',
blockChainHeight: status.chain.height,
syncPercentage: Math.round(status.chain.progress * 100),
height: status.chain.height,
error: null,
type: 'bcoin node',
});
}); });
}); });
// Copied from previous source
router.get('/peer', (req, res) => { router.get('/peer', (req, res) => {
res.json({ res.json({
connected: true, connected: true,

View File

@ -1,176 +1,217 @@
const Block = require('../../models/block.js');
const Transaction = require('../../models/transaction');
const logger = require('../logger'); const logger = require('../logger');
const request = require('request');
const config = require('../../config');
const db = require('../db');
const util = require('../util');
const MAX_TXS = 20; const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`;
const MAX_BLOCKS = 1; const MAX_TXS = config.api.max_txs;
const TTL = config.api.request_ttl;
// Shoe horned in. Not dry, also in blocks. Make db api later
function getBlock(params, options, cb) {
const defaultOptions = { _id: 0 };
Object.assign(defaultOptions, options);
Block.find(
params,
defaultOptions,
cb)
.sort({ height: -1 })
.limit(MAX_BLOCKS);
}
function getTransactions(params, options, cb) {
const defaultOptions = { _id: 0 };
Object.assign(defaultOptions, options);
Transaction.find(
params,
defaultOptions,
cb)
.sort({ height: 1 })
.limit(MAX_TXS);
}
module.exports = function transactionAPI(router) { module.exports = function transactionAPI(router) {
// Txs by txid
router.get('/tx/:txid', (req, res) => { router.get('/tx/:txid', (req, res) => {
getTransactions( if (!util.isTxid(req.params.txid)) {
{ hash: req.params.txid }, return res.status(400).send({
{ }, error: 'Invalid transaction id',
(err, tx) => { });
if (err) { }
res.status(501).send();
logger.log('err', err);
}
if (tx[0]) {
const t = tx[0];
// Map bcoin model to insight-api // Get max block height for calculating confirmations
res.json({ const height = db.blocks.bestHeight();
txid: t.hash, // Bcoin transaction data
version: t.version, return request(`${API_URL}/tx/${req.params.txid}`,
locktime: t.lockTime, { timeout: TTL },
vin: t.inputs.map(input => ({ (error, localRes, tx) => {
coinbase: input.script, if (error) {
sequence: input.sequence, logger.log('error',
n: 0, `${error}`);
})), return res.status(404).send();
vout: t.outputs.map(output => ({
value: output.value / 1e8,
n: 0,
scriptPubKey: {
hex: output.script,
asm: '',
addresses: [output.address],
type: null,
},
spentTxId: null,
spentIndex: null,
spentHeight: null,
})),
blockhash: t.block,
blockheight: t.height,
confirmations: 0,
time: 0,
blocktime: 0,
isCoinBase: false,
valueOut: t.outputs.reduce((a, b) => a.value + b.value).value / 1e8,
size: 0,
});
} else {
res.send();
} }
// Catch JSON errors
try {
tx = JSON.parse(tx);
} catch (e) {
logger.log('error',
`${e}`);
return res.status(404).send();
}
if (!tx || !tx.hash) {
logger.log('error',
'No results found');
return res.status(404).send();
}
// Return UI JSON
return res.send({
txid: tx.hash,
version: tx.version,
time: tx.ps,
blocktime: tx.ps,
locktime: tx.locktime,
blockhash: tx.block,
fees: tx.fee / 1e8,
confirmations: (height - tx.height) + 1,
valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8,
vin: tx.inputs.map(input => ({
addr: input.coin ? input.coin.address : '',
value: input.coin ? input.coin.value / 1e8 : 0,
scriptSig: {
asm: input.script,
},
})),
vout: tx.outputs.map(output => ({
scriptPubKey: {
asm: output.script,
addresses: [output.address],
},
value: output.value / 1e8,
})),
isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000',
});
}); });
}); });
// /txs is overloaded. Next ver separate concerns
// query by block
// query by address
// last n txs
router.get('/txs', (req, res) => { router.get('/txs', (req, res) => {
/* const pageNum = parseInt(req.query.pageNum, 10) || 0;
const txsBy = req.query.blocks || const rangeStart = pageNum * MAX_TXS;
req.query.address; const rangeEnd = rangeStart + MAX_TXS;
*/ // get txs for blockhash, start with best height to calc confirmations
if (req.query.block) { if (req.query.block) {
getBlock( if (!util.isBlockHash(req.query.block)) {
{ hash: req.query.block }, return res.status(400).send({
{ rawBlock: 0 }, error: 'Invalid block hash',
(err, block) => { });
if (err) { }
res.status(501).send(); const height = db.blocks.bestHeight();
logger.log('err', err); // Get Bcoin data
return request(`${API_URL}/block/${req.query.block}`,
{ timeout: TTL },
(error, localRes, block) => {
if (error) {
logger.log('error',
`${error}`);
return res.status(404).send();
} }
if (block[0]) { // Catch JSON errors
const b = block[0]; try {
res.json({ block = JSON.parse(block);
pagesTotal: 1, } catch (e) {
txs: b.txs.map(tx => ({ logger.log('error',
txid: tx.hash, `${e}`);
version: tx.version, return res.status(404).send();
locktime: tx.locktime, }
vin: tx.inputs.map(input => ({
coinbase: input.script, if (block.error) {
sequence: input.sequence, logger.log('error',
n: 0, `${'No tx results'}`);
addr: input.address, return res.status(404).send();
})), }
vout: tx.outputs.map(output => ({ // Setup UI JSON
value: output.value / 1e8, const totalPages = Math.ceil(block.txs.length / MAX_TXS);
n: 0, block.txs = block.txs.slice(rangeStart, rangeEnd);
scriptPubKey: {
hex: '', return res.send({
asm: '', pagesTotal: totalPages,
addresses: [output.address], txs: block.txs.map(tx => ({
type: output.type, txid: tx.hash,
}, fees: tx.fee / 1e8,
spentTxid: '', confirmations: (height - block.height) + 1,
spentIndex: 0, valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8,
spentHeight: 0, vin: tx.inputs.map(input => ({
})), addr: input.coin ? input.coin.address : '',
value: input.coin ? input.coin.value / 1e8 : 0,
scriptSig: {
asm: input.script,
},
})), })),
}); vout: tx.outputs.map(output => ({
} else { scriptPubKey: {
res.send(); asm: output.script,
} addresses: [output.address],
},
value: output.value / 1e8,
})),
isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000',
})),
});
}); });
} else if (req.query.address) { } else if (req.query.address) {
if (!util.isBitcoinAddress(req.query.address)) {
return res.status(400).send({
error: 'Invalid bitcoin address',
});
}
} else { // Get txs by address, start with best height to calc confirmations
getTransactions( const height = db.blocks.bestHeight();
{}, const addr = req.query.address || '';
{},
(err, txs) => { logger.log('debug',
if (err) { 'Warning: Requesting data from Bcoin by address, may take some time');
res.status(501).send();
return request(`${API_URL}/tx/address/${addr}`,
{ timeout: TTL },
(error, localRes, txs) => {
if (error) {
logger.log('error',
`${error}`);
return res.status(404).send();
} }
res.json({ // Catch JSON errors
try {
txs = JSON.parse(txs);
} catch (e) {
logger.log('error',
`${e}`);
return res.status(404).send();
}
// Bcoin returns error as part of data object
if (txs.error) {
logger.log('error',
`${'No tx results'}`);
return res.status(404).send();
}
// Setup UI JSON
return res.send({
pagesTotal: 1, pagesTotal: 1,
txs: txs.map(tx => ({ txs: txs.map(tx => ({
txid: tx.hash, txid: tx.hash,
version: tx.version, fees: tx.fee / 1e8,
locktime: tx.locktime, confirmations: (height - tx.height) + 1,
valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8,
vin: tx.inputs.map(input => ({ vin: tx.inputs.map(input => ({
coinbase: input.script, addr: input.coin ? input.coin.address : '',
sequence: input.sequence, value: input.coin ? input.coin.value / 1e8 : 0,
n: 0, scriptSig: {
asm: input.script,
},
})), })),
vout: tx.outputs.map(output => ({ vout: tx.outputs.map(output => ({
value: output.value,
n: 0,
scriptPubKey: { scriptPubKey: {
hex: '', asm: output.script,
asm: '',
addresses: [output.address], addresses: [output.address],
type: output.type,
}, },
spentTxid: '', value: output.value / 1e8,
spentIndex: 0,
spentHeight: 0,
})), })),
isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000',
})), })),
}); });
}, });
);
} }
// Get last n txs
db.txs.getTopTransactions((err, txs) => {
if (err) {
logger.log('err',
`/txs getTopTransactions ${err}`);
return res.status(404).send(err);
}
return res.json(txs);
});
}); });
router.get('/rawtx/:txid', (req, res) => { router.get('/rawtx/:txid', (req, res) => {
@ -178,6 +219,19 @@ module.exports = function transactionAPI(router) {
}); });
router.post('/tx/send', (req, res) => { router.post('/tx/send', (req, res) => {
res.send('tx send stub'); const rawtx = req.body.rawtx || '';
request.post({
url: `${API_URL}/broadcast`,
body: { tx: rawtx },
json: true,
}, (err, localRes, body) => {
if (err) {
logger.log('error',
`${err}`);
return res.status(400).send(err);
}
res.json(true);
});
}); });
}; };

85
server/lib/db/blocks.js Normal file
View File

@ -0,0 +1,85 @@
const Block = require('../../models/block.js');
const logger = require('../logger');
const config = require('../../config');
const MAX_BLOCKS = config.api.max_blocks; // ~ 12 hours
let bestBlockHeight = 0;
function getBlocks(params, options, limit, cb) {
// Do not return mongo ids
const defaultOptions = { _id: 0 };
// Copy over mongo options
Object.assign(defaultOptions, options);
// Simple sanitizing
if (!Number.isInteger(limit)) {
limit = 1;
}
if (limit > MAX_BLOCKS) {
limit = MAX_BLOCKS;
}
if (limit < 1) {
limit = 1;
}
// Query mongo
Block.find(
params,
defaultOptions,
(err, blocks) => {
if (err) {
logger.log('error',
`getBlocks: ${err}`);
return cb(err);
}
if (!blocks.length > 0) {
return cb({ err: 'Block not found' });
}
return cb(null, blocks);
})
.sort({ height: -1 })
.limit(limit);
}
// Retrieve a single block. For convenience mostly
function getBlock(params, options, limit, cb) {
getBlocks(params, options, limit, (err, blocks) => {
if (err) {
logger.log('error',
`getBlock: ${err.err}`);
return cb(err);
}
if (!blocks.length > 0) {
return cb({ err: 'Block not found' });
}
return cb(null, blocks[0]);
});
}
// Highest known height in mongo
function getBestHeight() {
getBlock({}, {}, 1, (err, block) => {
if (err) {
return logger.log('error',
`getBestHeight: ${err.err}`);
}
bestBlockHeight = block.height;
});
}
// 1e9 limit = ~2M years from now
// Mostly for sync to set height
function bestHeight(height) {
if (Number.isInteger(height) &&
height > 0 &&
height < 1 * 1e9) {
bestBlockHeight = height;
return bestBlockHeight;
}
return bestBlockHeight;
}
module.exports = {
getBlock,
getBlocks,
bestHeight,
};

View File

@ -1,5 +1,7 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const logger = require('../logger'); const logger = require('../logger');
const Blocks = require('./blocks');
const Txs = require('./transactions');
mongoose.connection.on('error', (err) => { mongoose.connection.on('error', (err) => {
logger.log('error', logger.log('error',
@ -7,4 +9,9 @@ mongoose.connection.on('error', (err) => {
${err}`); ${err}`);
}); });
module.exports = mongoose; module.exports = {
connect: mongoose.connect,
connection: mongoose.connection,
blocks: Blocks,
txs: Txs,
};

View File

@ -0,0 +1,89 @@
const Transactions = require('../../models/transaction.js');
const logger = require('../logger');
const config = require('../../config');
// For now, blocks handles these calls.
// These will be replaced with more advanced mongo
// No optimization yet.
const MAX_TXS = config.api.max_txs;
const MAX_PAGE_TXS = config.api.max_page_txs;
// For Paging
function getTransactions(params, options, limit, cb) {
// Do not return mongo ids
const defaultOptions = { _id: 0 };
// Copy over mongo options
Object.assign(defaultOptions, options);
// Simple sanitizing
if (!Number.isInteger(limit)) {
limit = 1;
}
if (limit > MAX_PAGE_TXS) {
limit = MAX_PAGE_TXS;
}
if (limit < 1) {
limit = 1;
}
// Query mongo
Transactions.find(
params,
defaultOptions,
(err, txs) => {
if (err) {
logger.log('error',
`getTransactions: ${err}`);
return cb(err);
}
if (!txs.length > 0) {
return cb({ err: 'Tx not found' });
}
return cb(null, txs);
})
.sort({ height: -1 })
.limit(limit);
}
function getTransaction(params, options, limit, cb) {
getTransactions(params, options, limit, (err, tx) => {
if (err) {
logger.log('error',
`getTransaction: ${err.err}`);
return cb(err);
}
if (!tx.length > 0) {
return cb({ err: 'Tx not found' });
}
return cb(null, tx[0]);
});
}
// Req Change, refactor above
function getTopTransactions(cb) {
// Do not return mongo ids
const defaultOptions = { _id: 0 };
// Query mongo
Transactions.find(
{},
(err, txs) => {
if (err) {
logger.log('error',
`getTransactions: ${err}`);
return cb(err);
}
if (!txs.length > 0) {
return cb({ err: 'Tx not found' });
}
return cb(null, txs);
})
.sort({ height: -1 })
.limit(MAX_TXS);
}
module.exports = {
getTransaction,
getTransactions,
getTopTransactions,
};

View File

@ -1,7 +1,7 @@
const winston = require('winston'); const winston = require('winston');
const config = require('../../config'); const config = require('../../config');
const logfile = new Date().toISOString(); const logfile = new Date().toISOString().split('T')[0];
const logger = new (winston.Logger)({ const logger = new (winston.Logger)({
transports: [ transports: [

View File

@ -1,20 +1,13 @@
const FullNode = require('bcoin/lib/node/fullnode'); const FullNode = require('bcoin/lib/node/fullnode');
const logger = require('../../lib/logger'); const logger = require('../../lib/logger');
const BlockParser = require('../parser').Block; const BlockParser = require('../parser').Block;
const TxParser = require('../parser').Transaction; const TxParser = require('../parser').Transaction;
const addrParser = require('../parser').Address;
const config = require('../../config'); const config = require('../../config');
const io = require('../api').io; const socket = require('../../lib/api/socket');
const db = require('../../lib/db');
const node = new FullNode(config.bcoin); const node = new FullNode(config.bcoin);
// Hacky move this to config
let refreshBlocks = false;
// Super Hacky but better than inline Maths.
setInterval(() => {
refreshBlocks = true;
}, 10000); // Only refresh sockets after 5s passes
function start() { function start() {
node.open() node.open()
.then(() => { .then(() => {
@ -25,21 +18,10 @@ function start() {
}); });
node.chain.on('connect', (entry, block) => { node.chain.on('connect', (entry, block) => {
BlockParser.parse(entry, block); BlockParser.parse(entry, block);
TxParser.parse(entry, block.txs); TxParser.parse(entry, block.txs);
addrParser.parse(entry, block.txs); socket.processBlock(entry, block);
db.blocks.bestHeight(entry.height);
if (refreshBlocks) {
refreshBlocks = false;
io.sockets.emit('block', {
hash: block.toJSON().hash,
});
}
});
node.pool.on('peer', (peer) => {
}); });
node.on('error', (err) => { node.on('error', (err) => {
@ -48,7 +30,7 @@ function start() {
}); });
node.mempool.on('tx', (tx) => { node.mempool.on('tx', (tx) => {
socket.emitTx(tx);
}); });
} }

View File

@ -1,40 +0,0 @@
const AddressModel = require('../../models/address');
const InputModel = require('../../models/input');
const OutputModel = require('../../models/output');
const config = require('../../config');
const util = require('../../lib/util');
const logger = require('../logger');
function parse(entry, txs) {
txs.forEach((tx) => {
//const txJSON = tx.toJSON();
tx.outputs.forEach((output) => {
const outputJSON = output.toJSON();
/*
return new OutputModel({
address: outputJSON.address,
script: outputJSON.script,
value: outputJSON.value,
});*/
});
tx.inputs.forEach((input) => {
const inputJSON = input.toJSON();
/* return new InputModel({
prevout: inputJSON.prevout,
script: inputJSON.script,
witness: inputJSON.witness,
sequence: inputJSON.sequence,
address: inputJSON.address,
});
})*/
});
});
}
module.exports = {
parse,
};

View File

@ -1,67 +1,32 @@
const BlockModel = require('../../models/block'); const BlockModel = require('../../models/block');
const InputModel = require('../../models/input'); const config = require('../../config');
const OutputModel = require('../../models/output'); const util = require('../../lib/util');
const config = require('../../config'); const logger = require('../logger');
const util = require('../../lib/util');
const logger = require('../logger');
function parse(entry, block) { function parse(entry, block) {
const rawBlock = block.toRaw().toString('hex'); const rawBlock = block.toRaw().toString('hex');
const blockJSON = block.toJSON(); const blockJSON = block.toJSON();
const reward = util.calcBlockReward(entry.height); const reward = util.calcBlockReward(entry.height);
// Can probably use destructuring to build something nicer // Can probably use destructuring to build something nicer
const newBlock = new BlockModel({ const newBlock = new BlockModel({
hash: blockJSON.hash, hash: blockJSON.hash,
height: entry.height, height: entry.height,
size: block.getSize(), size: block.getSize(),
version: blockJSON.version, version: blockJSON.version,
prevBlock: blockJSON.prevBlock, prevBlock: blockJSON.prevBlock,
merkleRoot: blockJSON.merkleRoot, merkleRoot: blockJSON.merkleRoot,
ts: blockJSON.ts, ts: blockJSON.ts,
bits: blockJSON.bits, bits: blockJSON.bits,
nonce: blockJSON.nonce, nonce: blockJSON.nonce,
txs: block.txs.map((tx) => { txs: block.txs.map((tx) => {
const txJSON = tx.toJSON(); const txJSON = tx.toJSON();
return { return txJSON.hash;
hash: txJSON.hash,
witnessHash: txJSON.witnessHash,
fee: txJSON.fee,
rate: txJSON.rate,
ps: txJSON.ps,
height: entry.height,
block: util.revHex(entry.hash),
ts: entry.ts,
date: txJSON.date,
index: txJSON.index,
version: txJSON.version,
flag: txJSON.flag,
inputs: tx.inputs.map((input) => {
const inputJSON = input.toJSON();
return new InputModel({
prevout: inputJSON.prevout,
script: inputJSON.script,
witness: inputJSON.witness,
sequence: inputJSON.sequence,
address: inputJSON.address,
});
}),
outputs: tx.outputs.map((output) => {
const outputJSON = output.toJSON();
return new OutputModel({
address: outputJSON.address,
script: outputJSON.script,
value: outputJSON.value,
});
}),
lockTime: txJSON.locktime,
chain: config.bcoin.network,
};
}), }),
chainwork: entry.chainwork, chainwork: entry.chainwork,
reward, reward,
network: config.bcoin.network, network: config.bcoin.network,
poolInfo: {}, poolInfo: {},
rawBlock, rawBlock,
}); });

View File

@ -1,9 +1,7 @@
const Block = require('./block'); const Block = require('./block');
const Transaction = require('./transaction'); const Transaction = require('./transaction');
const Address = require('./address');
module.exports = { module.exports = {
Block, Block,
Transaction, Transaction,
Address,
}; };

View File

@ -1,68 +1,59 @@
const TxModel = require('../../models/transaction'); const TxModel = require('../../models/transaction');
const InputModel = require('../../models/input'); const InputModel = require('../../models/input');
const OutputModel = require('../../models/output'); const OutputModel = require('../../models/output');
const config = require('../../config'); const config = require('../../config');
const util = require('../../lib/util'); const util = require('../../lib/util');
const logger = require('../logger'); const logger = require('../logger');
const io = require('../api').io; const db = require('../db');
const socketThrottle = 100; // Bleh, Bcoin pulls in blocks 20 at a time
let counter = 0; // Crappy delay for now otherwise async saves
// could miss a tx if an input refs a block within
// the last 20 that hasn't saved.
// Aggregate stuff will replace all of this.
function parse(entry, txs) { function parse(entry, txs) {
txs.forEach((tx) => { txs.forEach((tx) => {
const txJSON = tx.toJSON(); const txJSON = tx.toJSON();
const txRAW = tx.toRaw();
counter++;
if (counter % socketThrottle === 0) {
io.sockets.emit('tx', {
txid: txJSON.hash,
valueOut: tx.outputs.reduce((sum, tx) => {
tx = tx.toJSON();
const valB = (tx.value || tx.valueOut.value || 0) / 1e8;
return sum + valB;
}, 0),
});
}
const t = new TxModel({ const t = new TxModel({
hash: txJSON.hash, hash: txJSON.hash,
witnessHash: txJSON.witnessHash, witnessHash: txJSON.witnessHash,
fee: txJSON.fee, fee: txJSON.fee,
rate: txJSON.rate, rate: txJSON.rate,
ps: txJSON.ps, size: txRAW.length,
height: entry.height, ps: txJSON.ps,
block: util.revHex(entry.hash), height: entry.height,
ts: entry.ts, block: util.revHex(entry.hash),
date: txJSON.date, ts: entry.ts,
index: txJSON.index, date: txJSON.date,
version: txJSON.version, index: txJSON.index,
flag: txJSON.flag, version: txJSON.version,
inputs: tx.inputs.map((input) => { flag: txJSON.flag,
inputs: tx.inputs.map((input) => {
const inputJSON = input.toJSON(); const inputJSON = input.toJSON();
return new InputModel({ return new InputModel({
prevout: inputJSON.prevout, prevout: inputJSON.prevout,
script: inputJSON.script, script: inputJSON.script,
witness: inputJSON.witness, witness: inputJSON.witness,
sequence: inputJSON.sequence, sequence: inputJSON.sequence,
address: inputJSON.address, address: inputJSON.address,
}); });
}), }),
outputs: tx.outputs.map((output) => { outputs: tx.outputs.map((output) => {
const outputJSON = output.toJSON(); const outputJSON = output.toJSON();
return new OutputModel({ return new OutputModel({
address: outputJSON.address, address: outputJSON.address,
script: outputJSON.script, script: outputJSON.script,
value: outputJSON.value, value: outputJSON.value,
}); });
}), }),
lockTime: txJSON.locktime, lockTime: txJSON.locktime,
chain: config.bcoin.network, chain: config.bcoin.network,
}); });
t.save((err) => { t.save((err) => {
if (err) { if (err) {
logger.log('error', err.message); logger.log('error', err.message);

View File

@ -21,8 +21,19 @@ function calcBlockReward(height) {
return reward; return reward;
} }
function is64HexString(value) {
return /^[0-9a-f]{64}$/i.test(value);
}
function isBitcoinAddress(value) {
return /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(value);
}
module.exports = { module.exports = {
revHex, revHex,
calcBlockReward, calcBlockReward,
isBlockHash: is64HexString,
isTxid: is64HexString,
isBitcoinAddress,
}; };

View File

@ -5,9 +5,9 @@ const Output = require('./output');
const Schema = mongoose.Schema; const Schema = mongoose.Schema;
const AddressSchema = new Schema({ const AddressSchema = new Schema({
address: String, address: { type: String, default: '' },
inputs: [Input.schema], inputs: [Input.schema],
outputs: [Output.schema], outputs: [Output.schema],
}); });
const Address = mongoose.model('Address', AddressSchema); const Address = mongoose.model('Address', AddressSchema);

View File

@ -1,24 +1,24 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const Transaction = require('./transaction'); const Transaction = require('./transaction');
const Schema = mongoose.Schema; const Schema = mongoose.Schema;
const BlockSchema = new Schema({ const BlockSchema = new Schema({
hash: String, hash: { type: String, default: '' },
height: Number, height: { type: Number, default: 0 },
size: Number, size: { type: Number, default: 0 },
version: Number, version: { type: Number, default: 0 },
prevBlock: String, prevBlock: { type: String, default: '' },
merkleRoot: String, merkleRoot: { type: String, default: '' },
ts: Number, ts: { type: Number, default: 0 },
bits: Number, bits: { type: Number, default: 0 },
nonce: Number, nonce: { type: Number, default: 0 },
txs: [Transaction.schema], txs: [{ type: String, default: '' }],
chainwork: Number, chainwork: { type: Number, default: 0 },
reward: Number, reward: { type: Number, default: 0 },
network: String, network: { type: String, default: '' },
poolInfo: Object, poolInfo: { type: Object, default: {} },
rawBlock: String, rawBlock: { type: String, default: '' },
}, { }, {
toJSON: { toJSON: {
virtuals: true, virtuals: true,
@ -26,6 +26,9 @@ const BlockSchema = new Schema({
id: false, id: false,
}); });
BlockSchema.index({ hash: 1 });
BlockSchema.index({ height: 1 });
const Block = mongoose.model('Block', BlockSchema); const Block = mongoose.model('Block', BlockSchema);
module.exports = Block; module.exports = Block;

View File

@ -3,13 +3,15 @@ const mongoose = require('mongoose');
const Schema = mongoose.Schema; const Schema = mongoose.Schema;
const InputSchema = new Schema({ const InputSchema = new Schema({
prevout: Object, prevout: { type: Object, default: {} },
script: String, script: { type: String, default: '' },
witness: String, witness: { type: String, default: '' },
sequence: Number, sequence: { type: Number, default: 0 },
address: String, address: { type: String, default: '' },
}); });
InputSchema.index({ address: 1 });
const Input = mongoose.model('Input', InputSchema); const Input = mongoose.model('Input', InputSchema);
module.exports = Input; module.exports = Input;

View File

@ -3,12 +3,14 @@ const mongoose = require('mongoose');
const Schema = mongoose.Schema; const Schema = mongoose.Schema;
const OutputSchema = new Schema({ const OutputSchema = new Schema({
address: String, address: { type: String, default: '' },
script: String, script: { type: String, default: '' },
value: Number, value: { type: Number, default: 0 },
type: String, type: { type: String, default: '' },
}); });
OutputSchema.index({ address: 1 });
const Output = mongoose.model('Output', OutputSchema); const Output = mongoose.model('Output', OutputSchema);
module.exports = Output; module.exports = Output;

View File

@ -1,27 +1,29 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const Input = require('./input'); const Input = require('./input');
const Output = require('./output'); const Output = require('./output');
const Schema = mongoose.Schema; const Schema = mongoose.Schema;
const TransactionSchema = new Schema({ const TransactionSchema = new Schema({
hash: String, hash: { type: String, default: '' },
witnessHash: String, witnessHash: { type: String, default: '' },
fee: Number, fee: { type: Number, default: 0 },
rate: Number, rate: { type: Number, default: 0 },
ps: Number, ps: { type: Number, default: 0 },
height: Number, height: { type: Number, default: 0 },
block: String, block: { type: String, default: '' },
index: Number, index: { type: Number, default: 0 },
version: Number, version: { type: Number, default: 0 },
flag: Number, flag: { type: Number, default: 0 },
lockTime: Number, lockTime: { type: Number, default: 0 },
inputs: [Input.schema], inputs: [Input.schema],
outputs: [Output.schema], outputs: [Output.schema],
size: Number, size: { type: Number, default: 0 },
network: String, network: { type: String, default: '' },
}); });
TransactionSchema.index({ hash: 1 });
const Transaction = mongoose.model('Transaction', TransactionSchema); const Transaction = mongoose.model('Transaction', TransactionSchema);
module.exports = Transaction; module.exports = Transaction;

View File

@ -14,6 +14,8 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bcoin": "^1.0.0-beta.14", "bcoin": "^1.0.0-beta.14",
"bitcore-message": "^1.0.4",
"body-parser": "^1.17.2",
"express": "^4.15.3", "express": "^4.15.3",
"mongoose": "^4.11.5", "mongoose": "^4.11.5",
"request": "^2.81.0", "request": "^2.81.0",

View File

@ -1123,7 +1123,6 @@ ScopedSocket.prototype.emit = function(event, data, callback) {
angular.module('insight.socket').factory('getSocket', angular.module('insight.socket').factory('getSocket',
function($rootScope) { function($rootScope) {
console.log('init my socket');
var socket = io.connect(null, { var socket = io.connect(null, {
'reconnect': true, 'reconnect': true,
'reconnection delay': 500, 'reconnection delay': 500,

File diff suppressed because one or more lines are too long

View File

@ -55,7 +55,6 @@ ScopedSocket.prototype.emit = function(event, data, callback) {
angular.module('insight.socket').factory('getSocket', angular.module('insight.socket').factory('getSocket',
function($rootScope) { function($rootScope) {
console.log('init my socket');
var socket = io.connect('http://localhost', { var socket = io.connect('http://localhost', {
'reconnect': true, 'reconnect': true,
'reconnection delay': 500, 'reconnection delay': 500,