Merge branch 'next' of https://github.com/bitpay/insight into next
This commit is contained in:
commit
c6b0e66a42
@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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, () => {
|
||||||
|
|||||||
@ -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');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
module.exports = function (req, res, next) {
|
module.exports = function (req, res, next) {
|
||||||
let allowed = {
|
const allowed = {
|
||||||
|
|
||||||
origins: [
|
origins: [
|
||||||
'*',
|
'*',
|
||||||
|
|||||||
@ -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,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
85
server/lib/db/blocks.js
Normal 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,
|
||||||
|
};
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
|||||||
89
server/lib/db/transactions.js
Normal file
89
server/lib/db/transactions.js
Normal 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,
|
||||||
|
};
|
||||||
@ -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: [
|
||||||
|
|||||||
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
|
||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
2
server/public/js/main.min.js
vendored
2
server/public/js/main.min.js
vendored
File diff suppressed because one or more lines are too long
@ -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,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user