commit
421d0ef8f4
@ -11,7 +11,7 @@ import 'rxjs/add/operator/map';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ApiProvider {
|
export class ApiProvider {
|
||||||
|
|
||||||
public apiPrefix: string = 'https://insight.bitpay.com/api/';
|
public apiPrefix: string = '/api/';
|
||||||
|
|
||||||
constructor(public http: Http) {
|
constructor(public http: Http) {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export class BlocksService {
|
|||||||
constructor(private http: Http) {}
|
constructor(private http: Http) {}
|
||||||
|
|
||||||
public getLatestBlocks(): void {
|
public getLatestBlocks(): void {
|
||||||
this.http.request('https://insight.bitpay.com/api/blocks').subscribe((res: Response) => {
|
this.http.request('/api/blocks').subscribe((res: Response) => {
|
||||||
const data: {
|
const data: {
|
||||||
blocks: InsightBlockObject[],
|
blocks: InsightBlockObject[],
|
||||||
length: number,
|
length: number,
|
||||||
|
|||||||
@ -28,7 +28,7 @@ const config = {
|
|||||||
ticker_url: 'https://www.bitstamp.net/api/ticker/',
|
ticker_url: 'https://www.bitstamp.net/api/ticker/',
|
||||||
ticker_prop: 'bitstamp',
|
ticker_prop: 'bitstamp',
|
||||||
max_blocks: 72,
|
max_blocks: 72,
|
||||||
max_txs: 50,
|
max_txs: 10,
|
||||||
max_page_txs: 10,
|
max_page_txs: 10,
|
||||||
request_ttl: 100000,
|
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');
|
const Api = require('./lib/api').server;
|
||||||
const db = require('./lib/db');
|
const db = require('./lib/db');
|
||||||
|
|
||||||
logger.log('debug',
|
logger.log('debug',
|
||||||
|
|||||||
@ -1,72 +1,55 @@
|
|||||||
const logger = require('../logger');
|
const logger = require('../logger');
|
||||||
const request = require('request');
|
|
||||||
const config = require('../../config');
|
|
||||||
const util = require('../util');
|
const util = require('../util');
|
||||||
|
const db = require('../db');
|
||||||
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) => {
|
||||||
const addr = req.params.addr || '';
|
const addr = req.params.addr || '';
|
||||||
|
|
||||||
if (!util.isBitcoinAddress(addr)) {
|
if (!util.isBitcoinAddress(addr)) {
|
||||||
return res.status(400).send({
|
return res.status(404).send({
|
||||||
error: 'Invalid bitcoin address',
|
error: 'Invalid bitcoin address',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log('debug',
|
return db.txs.getTxByAddress(addr, 0, 999999999, (error, txs) => {
|
||||||
'Warning: Requesting data from Bcoin by address, may take some time');
|
if (error) {
|
||||||
// Get Bcoin data
|
logger.log('error',
|
||||||
return request(`${API_URL}/tx/address/${addr}`,
|
`getTxByBlock ${error}`);
|
||||||
{ timeout: TTL },
|
return res.status(404).send();
|
||||||
(error, bcoinRes, bcoinTxs) => {
|
}
|
||||||
if (error) {
|
|
||||||
logger.log('error',
|
// Sum the matching outputs for every tx
|
||||||
`${error}`);
|
const totalReceived = txs.reduce((total, tx) => total + tx.outputs.reduce((sum, output) => {
|
||||||
return res.status(404).send({});
|
if (output.address === req.params.addr) {
|
||||||
|
return sum + output.value;
|
||||||
}
|
}
|
||||||
let txs = {};
|
return sum;
|
||||||
try {
|
}, 0), 0) || 0;
|
||||||
txs = JSON.parse(bcoinTxs);
|
|
||||||
} catch (e) {
|
// Sum the matching inputs for every tx
|
||||||
logger.log('error',
|
const totalSpent = txs.reduce((total, tx) => total + tx.inputs.reduce((sum, input) => {
|
||||||
`${e}`);
|
if (input.coin && input.coin.address === req.params.addr) {
|
||||||
return res.status(404).send({});
|
return sum + input.coin.value;
|
||||||
}
|
}
|
||||||
|
return sum;
|
||||||
|
}, 0), 0) || 0;
|
||||||
|
|
||||||
// Sum the matching outputs for every tx
|
// Match Insight API
|
||||||
const totalReceived = txs.reduce((total, tx) => total + tx.outputs.reduce((sum, output) => {
|
return res.json({
|
||||||
if (output.address === req.params.addr) {
|
addrStr: req.params.addr,
|
||||||
return sum + output.value;
|
balance: (totalReceived - totalSpent) / 1e8,
|
||||||
}
|
balanceSat: totalReceived - totalSpent,
|
||||||
return sum;
|
totalReceived: totalReceived / 1e8,
|
||||||
}, 0), 0) || 0;
|
totalReceivedSat: totalReceived,
|
||||||
|
totalSent: totalSpent / 1e8,
|
||||||
// Sum the matching inputs for every tx
|
totalSentSat: totalSpent,
|
||||||
const totalSpent = txs.reduce((total, tx) => total + tx.inputs.reduce((sum, input) => {
|
unconfirmedBalance: 0,
|
||||||
if (input.coin && input.coin.address === req.params.addr) {
|
unconfirmedBalanceSat: 0,
|
||||||
return sum + input.coin.value;
|
unconfirmedTxApperances: 0,
|
||||||
}
|
txApperances: txs.length,
|
||||||
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
|
// Stubbed by # to help with tasking
|
||||||
|
|||||||
@ -7,18 +7,15 @@ module.exports = function BlockAPI(router) {
|
|||||||
const blockHash = req.params.blockHash;
|
const blockHash = req.params.blockHash;
|
||||||
|
|
||||||
if (!util.isBlockHash(blockHash)) {
|
if (!util.isBlockHash(blockHash)) {
|
||||||
return res.status(400).send({
|
return res.status(404).send({
|
||||||
error: 'Invalid bitcoin address',
|
error: 'Invalid bitcoin address',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass Mongo params, fields and limit to db api.
|
// Pass Mongo params, fields and limit to db api.
|
||||||
db.blocks.getBlock(
|
return db.blocks.getByHash(blockHash,
|
||||||
{ hash: blockHash },
|
|
||||||
{ rawBlock: 0 },
|
|
||||||
1,
|
|
||||||
(err, block) => {
|
(err, block) => {
|
||||||
if (err) {
|
if (err || !block) {
|
||||||
logger.log('err', err);
|
logger.log('err', err);
|
||||||
return res.status(404).send();
|
return res.status(404).send();
|
||||||
}
|
}
|
||||||
@ -46,21 +43,11 @@ module.exports = function BlockAPI(router) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/blocks', (req, res) => {
|
router.get('/blocks', (req, res) => {
|
||||||
const limit = parseInt(req.query.limit, 10) || 100;
|
|
||||||
// Pass Mongo params, fields and limit to db api.
|
// Pass Mongo params, fields and limit to db api.
|
||||||
db.blocks.getBlocks(
|
db.blocks.getTopBlocks(
|
||||||
{},
|
|
||||||
{ height: 1,
|
|
||||||
size: 1,
|
|
||||||
hash: 1,
|
|
||||||
ts: 1,
|
|
||||||
txs: 1,
|
|
||||||
poolInfo: 1,
|
|
||||||
},
|
|
||||||
limit,
|
|
||||||
(err, blocks) => {
|
(err, blocks) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.log('err',
|
logger.log('error',
|
||||||
`/blocks: ${err}`);
|
`/blocks: ${err}`);
|
||||||
return res.status(404).send();
|
return res.status(404).send();
|
||||||
}
|
}
|
||||||
@ -90,13 +77,10 @@ module.exports = function BlockAPI(router) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pass Mongo params, fields and limit to db api.
|
// Pass Mongo params, fields and limit to db api.
|
||||||
db.blocks.getBlock(
|
return db.blocks.getRawBlock(blockHash,
|
||||||
{ hash: blockHash },
|
|
||||||
{ rawBlock: 1 },
|
|
||||||
1,
|
|
||||||
(err, block) => {
|
(err, block) => {
|
||||||
if (err) {
|
if (err || !block) {
|
||||||
logger.log('err',
|
logger.log('error',
|
||||||
`/rawblock/:blockHash: ${err}`);
|
`/rawblock/:blockHash: ${err}`);
|
||||||
return res.status(404).send();
|
return res.status(404).send();
|
||||||
}
|
}
|
||||||
@ -105,15 +89,12 @@ module.exports = function BlockAPI(router) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.get('/block-index/:height', (req, res) => {
|
router.get('/block-index/:height', (req, res) => {
|
||||||
const blockHeight = parseInt(req.params.height, 10) || 1;
|
const height = parseInt(req.params.height, 10) || 1;
|
||||||
// Pass Mongo params, fields and limit to db api.
|
// Pass Mongo params, fields and limit to db api.
|
||||||
db.blocks.getBlock(
|
return db.blocks.byHeight(height,
|
||||||
{ height: blockHeight },
|
|
||||||
{ hash: 1 },
|
|
||||||
1,
|
|
||||||
(err, block) => {
|
(err, block) => {
|
||||||
if (err) {
|
if (err || !block) {
|
||||||
logger.log('err',
|
logger.log('error',
|
||||||
`/block-index/:height: ${err}`);
|
`/block-index/:height: ${err}`);
|
||||||
return res.status(404).send();
|
return res.status(404).send();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,21 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const config = require('../../config');
|
const config = require('../../config');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
|
const helmet = require('helmet');
|
||||||
|
|
||||||
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(helmet());
|
||||||
app.use(bodyParser.urlencoded({ extended: false }));
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
app.use(bodyParser.json());
|
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('../app/www', { maxage: '1w' }));
|
||||||
app.use('/:stuff', express.static('./public'));
|
// Legacy UI - useful for 1:1 compares
|
||||||
app.use('/blocks', express.static('./public'));
|
// app.use(express.static('./public', { maxage: '1w' }));
|
||||||
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('/block/:blockhash', express.static('./public'));
|
|
||||||
app.use('/tx/:txid', 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);
|
||||||
|
|
||||||
@ -34,18 +27,19 @@ const StatusAPI = require('./status')(api);
|
|||||||
const TransactionAPI = require('./transaction')(api);
|
const TransactionAPI = require('./transaction')(api);
|
||||||
const MessageAPI = require('./message')(api);
|
const MessageAPI = require('./message')(api);
|
||||||
|
|
||||||
app.use('/insight-api', api);
|
app.use('/api', api);
|
||||||
|
|
||||||
// 404
|
// 404
|
||||||
app.use((req, res) => {
|
app.use((req, res) => res.status(404).send({
|
||||||
res.status(404).send({
|
status: 404,
|
||||||
status: 404,
|
url: req.originalUrl,
|
||||||
url: req.originalUrl,
|
error: 'Not found',
|
||||||
error: 'Not found',
|
}));
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Socket server
|
// Socket server
|
||||||
const server = require('http').Server(app);
|
const server = require('http').Server(app);
|
||||||
|
|
||||||
module.exports = server;
|
module.exports = {
|
||||||
|
server,
|
||||||
|
api,
|
||||||
|
};
|
||||||
|
|||||||
@ -27,7 +27,7 @@ function verifyMessage(req, res) {
|
|||||||
code: 1,
|
code: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
res.json({ result: valid });
|
return res.json({ result: valid });
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function messageAPI(router) {
|
module.exports = function messageAPI(router) {
|
||||||
@ -39,7 +39,5 @@ module.exports = function messageAPI(router) {
|
|||||||
verifyMessage(req, res);
|
verifyMessage(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/utils/estimatefee', (req, res) => {
|
router.get('/utils/estimatefee', (req, res) => res.send('estimate fees'));
|
||||||
res.send('estimate fees');
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,10 +30,7 @@ module.exports = function statusAPI(router) {
|
|||||||
// Get last block hash or node status
|
// Get last block hash or node status
|
||||||
router.get('/status', (req, res) => {
|
router.get('/status', (req, res) => {
|
||||||
if (req.query.q === 'getLastBlockHash') {
|
if (req.query.q === 'getLastBlockHash') {
|
||||||
db.blocks.getBlock(
|
db.blocks.getLastBlock(
|
||||||
{},
|
|
||||||
{ hash: 1 },
|
|
||||||
1,
|
|
||||||
(err, block) => {
|
(err, block) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.log('error',
|
logger.log('error',
|
||||||
@ -48,12 +45,12 @@ module.exports = function statusAPI(router) {
|
|||||||
} else {
|
} else {
|
||||||
getStatus((err, status) => {
|
getStatus((err, status) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.log('err',
|
logger.log('error',
|
||||||
`/status getStatus: ${err}`);
|
`/status getStatus: ${err}`);
|
||||||
return res.status(404).send(err);
|
return res.status(404).send(err);
|
||||||
}
|
}
|
||||||
if (!status) {
|
if (!status) {
|
||||||
logger.log('err',
|
logger.log('error',
|
||||||
'/status getStatus: no Status');
|
'/status getStatus: no Status');
|
||||||
return res.status(404).send();
|
return res.status(404).send();
|
||||||
}
|
}
|
||||||
@ -79,12 +76,12 @@ module.exports = function statusAPI(router) {
|
|||||||
router.get('/sync', (req, res) => {
|
router.get('/sync', (req, res) => {
|
||||||
getStatus((err, status) => {
|
getStatus((err, status) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
logger.log('err',
|
logger.log('error',
|
||||||
`/sync: ${err}`);
|
`/sync: ${err}`);
|
||||||
return res.status(404).send(err);
|
return res.status(404).send(err);
|
||||||
}
|
}
|
||||||
if (!status) {
|
if (!status) {
|
||||||
logger.log('err',
|
logger.log('error',
|
||||||
'/sync: no status');
|
'/sync: no status');
|
||||||
return res.status(404).send();
|
return res.status(404).send();
|
||||||
}
|
}
|
||||||
@ -99,17 +96,13 @@ module.exports = function statusAPI(router) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
// Copied from previous source
|
// Copied from previous source
|
||||||
router.get('/peer', (req, res) => {
|
router.get('/peer', (req, res) => res.json({
|
||||||
res.json({
|
connected: true,
|
||||||
connected: true,
|
host: '127.0.0.1',
|
||||||
host: '127.0.0.1',
|
port: null,
|
||||||
port: null,
|
}));
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/version', (req, res) => {
|
router.get('/version', (req, res) => res.json({
|
||||||
res.json({
|
version: pkg.version,
|
||||||
version: pkg.version,
|
}));
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,7 +12,7 @@ module.exports = function transactionAPI(router) {
|
|||||||
// Txs by txid
|
// Txs by txid
|
||||||
router.get('/tx/:txid', (req, res) => {
|
router.get('/tx/:txid', (req, res) => {
|
||||||
if (!util.isTxid(req.params.txid)) {
|
if (!util.isTxid(req.params.txid)) {
|
||||||
return res.status(400).send({
|
return res.status(404).send({
|
||||||
error: 'Invalid transaction id',
|
error: 'Invalid transaction id',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -20,45 +20,171 @@ module.exports = function transactionAPI(router) {
|
|||||||
// Get max block height for calculating confirmations
|
// Get max block height for calculating confirmations
|
||||||
const height = db.blocks.bestHeight();
|
const height = db.blocks.bestHeight();
|
||||||
// Bcoin transaction data
|
// Bcoin transaction data
|
||||||
return request(`${API_URL}/tx/${req.params.txid}`,
|
const txid = req.params.txid || '';
|
||||||
{ timeout: TTL },
|
|
||||||
(error, localRes, tx) => {
|
|
||||||
if (error) {
|
|
||||||
logger.log('error',
|
|
||||||
`${error}`);
|
|
||||||
return res.status(404).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
|
db.txs.getTxById(txid, (err, transaction) => {
|
||||||
return res.send({
|
if (err) {
|
||||||
|
logger.log('error',
|
||||||
|
`/tx/:tid getTxById: ${err.err}`);
|
||||||
|
return res.status(404).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
const tx = transaction;
|
||||||
|
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,
|
||||||
|
size: tx.size,
|
||||||
|
confirmations: (height - tx.height) + 1,
|
||||||
|
valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8,
|
||||||
|
vin: tx.inputs.map(input => ({
|
||||||
|
addr: input.address,
|
||||||
|
value: input.value / 1e8,
|
||||||
|
})),
|
||||||
|
vout: tx.outputs.map(output => ({
|
||||||
|
scriptPubKey: {
|
||||||
|
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
|
||||||
|
router.get('/txs', (req, res) => {
|
||||||
|
const pageNum = parseInt(req.query.pageNum, 10) || 0;
|
||||||
|
const rangeStart = pageNum * MAX_TXS;
|
||||||
|
const rangeEnd = rangeStart + MAX_TXS;
|
||||||
|
const height = db.blocks.bestHeight();
|
||||||
|
// get txs for blockhash, start with best height to calc confirmations
|
||||||
|
if (req.query.block) {
|
||||||
|
if (!util.isBlockHash(req.query.block)) {
|
||||||
|
return res.status(400).send({
|
||||||
|
error: 'Invalid block hash',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.txs.getTxCountByBlock(req.query.block, (err, count) => {
|
||||||
|
if (err) {
|
||||||
|
logger.log('error',
|
||||||
|
`getTxByBlock ${err}`);
|
||||||
|
return res.status(404).send();
|
||||||
|
}
|
||||||
|
const totalPages = Math.ceil(count / MAX_TXS);
|
||||||
|
|
||||||
|
return db.txs.getTxByBlock(req.query.block, pageNum, MAX_TXS, (error, txs) => {
|
||||||
|
if (error) {
|
||||||
|
logger.log('error',
|
||||||
|
`getTxByBlock ${error}`);
|
||||||
|
return res.status(404).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.send({
|
||||||
|
pagesTotal: totalPages,
|
||||||
|
txs: txs.map(tx => ({
|
||||||
|
txid: tx.hash,
|
||||||
|
fees: tx.fee / 1e8,
|
||||||
|
size: tx.size,
|
||||||
|
confirmations: (height - tx.height) + 1,
|
||||||
|
valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8,
|
||||||
|
vin: tx.inputs.map(input => ({
|
||||||
|
scriptSig: {
|
||||||
|
asm: input.script,
|
||||||
|
},
|
||||||
|
addr: input.address,
|
||||||
|
value: input.value / 1e8,
|
||||||
|
})),
|
||||||
|
vout: tx.outputs.map(output => ({
|
||||||
|
scriptPubKey: {
|
||||||
|
asm: output.script,
|
||||||
|
addresses: [output.address],
|
||||||
|
},
|
||||||
|
value: output.value / 1e8,
|
||||||
|
})),
|
||||||
|
isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (req.query.address) {
|
||||||
|
if (!util.isBitcoinAddress(req.query.address)) {
|
||||||
|
return res.status(400).send({
|
||||||
|
error: 'Invalid bitcoin address',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get txs by address, start with best height to calc confirmations
|
||||||
|
const addr = req.query.address || '';
|
||||||
|
|
||||||
|
db.txs.getTxCountByAddress(req.query.address, (err, count) => {
|
||||||
|
if (err) {
|
||||||
|
logger.log('error',
|
||||||
|
`getTxByBlock ${err}`);
|
||||||
|
return res.status(404).send();
|
||||||
|
}
|
||||||
|
const totalPages = Math.ceil(count / MAX_TXS);
|
||||||
|
|
||||||
|
return db.txs.getTxByAddress(req.query.address, pageNum, MAX_TXS, (error, txs) => {
|
||||||
|
if (error) {
|
||||||
|
logger.log('error',
|
||||||
|
`getTxByBlock ${error}`);
|
||||||
|
return res.status(404).send();
|
||||||
|
}
|
||||||
|
return res.send({
|
||||||
|
pagesTotal: totalPages,
|
||||||
|
txs: txs.map(tx => ({
|
||||||
|
txid: tx.hash,
|
||||||
|
fees: tx.fee / 1e8,
|
||||||
|
size: tx.size,
|
||||||
|
confirmations: (height - tx.height) + 1,
|
||||||
|
valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8,
|
||||||
|
vin: tx.inputs.map(input => ({
|
||||||
|
scriptSig: {
|
||||||
|
asm: input.script,
|
||||||
|
},
|
||||||
|
addr: input.address,
|
||||||
|
value: input.value / 1e8,
|
||||||
|
})),
|
||||||
|
vout: tx.outputs.map(output => ({
|
||||||
|
scriptPubKey: {
|
||||||
|
asm: output.script,
|
||||||
|
addresses: [output.address],
|
||||||
|
},
|
||||||
|
value: output.value / 1e8,
|
||||||
|
})),
|
||||||
|
isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000',
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 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.send(txs.map(tx => ({
|
||||||
txid: tx.hash,
|
txid: tx.hash,
|
||||||
version: tx.version,
|
|
||||||
time: tx.ps,
|
|
||||||
blocktime: tx.ps,
|
|
||||||
locktime: tx.locktime,
|
|
||||||
blockhash: tx.block,
|
|
||||||
fees: tx.fee / 1e8,
|
fees: tx.fee / 1e8,
|
||||||
|
size: tx.size,
|
||||||
confirmations: (height - tx.height) + 1,
|
confirmations: (height - tx.height) + 1,
|
||||||
valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8,
|
valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8,
|
||||||
vin: tx.inputs.map(input => ({
|
vin: tx.inputs.map(input => ({
|
||||||
addr: input.coin ? input.coin.address : '',
|
|
||||||
value: input.coin ? input.coin.value / 1e8 : 0,
|
|
||||||
scriptSig: {
|
scriptSig: {
|
||||||
asm: input.script,
|
asm: input.script,
|
||||||
},
|
},
|
||||||
|
addr: input.address,
|
||||||
|
value: input.value / 1e8,
|
||||||
})),
|
})),
|
||||||
vout: tx.outputs.map(output => ({
|
vout: tx.outputs.map(output => ({
|
||||||
scriptPubKey: {
|
scriptPubKey: {
|
||||||
@ -68,155 +194,13 @@ module.exports = function transactionAPI(router) {
|
|||||||
value: output.value / 1e8,
|
value: output.value / 1e8,
|
||||||
})),
|
})),
|
||||||
isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000',
|
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) => {
|
|
||||||
const pageNum = parseInt(req.query.pageNum, 10) || 0;
|
|
||||||
const rangeStart = pageNum * MAX_TXS;
|
|
||||||
const rangeEnd = rangeStart + MAX_TXS;
|
|
||||||
// get txs for blockhash, start with best height to calc confirmations
|
|
||||||
if (req.query.block) {
|
|
||||||
if (!util.isBlockHash(req.query.block)) {
|
|
||||||
return res.status(400).send({
|
|
||||||
error: 'Invalid block hash',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const height = db.blocks.bestHeight();
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
// Catch JSON errors
|
|
||||||
try {
|
|
||||||
block = JSON.parse(block);
|
|
||||||
} catch (e) {
|
|
||||||
logger.log('error',
|
|
||||||
`${e}`);
|
|
||||||
return res.status(404).send();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.error) {
|
|
||||||
logger.log('error',
|
|
||||||
`${'No tx results'}`);
|
|
||||||
return res.status(404).send();
|
|
||||||
}
|
|
||||||
// Setup UI JSON
|
|
||||||
const totalPages = Math.ceil(block.txs.length / MAX_TXS);
|
|
||||||
block.txs = block.txs.slice(rangeStart, rangeEnd);
|
|
||||||
|
|
||||||
return res.send({
|
|
||||||
pagesTotal: totalPages,
|
|
||||||
txs: block.txs.map(tx => ({
|
|
||||||
txid: tx.hash,
|
|
||||||
fees: tx.fee / 1e8,
|
|
||||||
confirmations: (height - block.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',
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else if (req.query.address) {
|
|
||||||
if (!util.isBitcoinAddress(req.query.address)) {
|
|
||||||
return res.status(400).send({
|
|
||||||
error: 'Invalid bitcoin address',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get txs by address, start with best height to calc confirmations
|
|
||||||
const height = db.blocks.bestHeight();
|
|
||||||
const addr = req.query.address || '';
|
|
||||||
|
|
||||||
logger.log('debug',
|
|
||||||
'Warning: Requesting data from Bcoin by address, may take some time');
|
|
||||||
|
|
||||||
return request(`${API_URL}/tx/address/${addr}`,
|
|
||||||
{ timeout: TTL },
|
|
||||||
(error, localRes, txs) => {
|
|
||||||
if (error) {
|
|
||||||
logger.log('error',
|
|
||||||
`${error}`);
|
|
||||||
return res.status(404).send();
|
|
||||||
}
|
|
||||||
// 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,
|
|
||||||
txs: txs.map(tx => ({
|
|
||||||
txid: tx.hash,
|
|
||||||
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',
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// 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) => res.send(req.params.txid));
|
||||||
res.send(req.params.txid);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/tx/send', (req, res) => {
|
router.post('/tx/send', (req, res) => {
|
||||||
const rawtx = req.body.rawtx || '';
|
const rawtx = req.body.rawtx || '';
|
||||||
@ -228,10 +212,10 @@ module.exports = function transactionAPI(router) {
|
|||||||
if (err) {
|
if (err) {
|
||||||
logger.log('error',
|
logger.log('error',
|
||||||
`${err}`);
|
`${err}`);
|
||||||
return res.status(400).send(err);
|
return res.status(404).send(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(true);
|
return res.json(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,70 +2,10 @@ const Block = require('../../models/block.js');
|
|||||||
const logger = require('../logger');
|
const logger = require('../logger');
|
||||||
const config = require('../../config');
|
const config = require('../../config');
|
||||||
|
|
||||||
const MAX_BLOCKS = config.api.max_blocks; // ~ 12 hours
|
const block = new Block();
|
||||||
|
|
||||||
let bestBlockHeight = 0;
|
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
|
// 1e9 limit = ~2M years from now
|
||||||
// Mostly for sync to set height
|
// Mostly for sync to set height
|
||||||
function bestHeight(height) {
|
function bestHeight(height) {
|
||||||
@ -78,8 +18,32 @@ function bestHeight(height) {
|
|||||||
return bestBlockHeight;
|
return bestBlockHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRawBlock(hash, cb) {
|
||||||
|
return block.getRawBlock(hash, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function byHeight(height, cb) {
|
||||||
|
return block.byHeight(height, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTopBlocks(cb) {
|
||||||
|
return block.last(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getByHash(hash, cb) {
|
||||||
|
return block.byHash(hash, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLastBlock(cb) {
|
||||||
|
return block.last(cb)
|
||||||
|
.limit(1);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getBlock,
|
getRawBlock,
|
||||||
getBlocks,
|
getTopBlocks,
|
||||||
|
getLastBlock,
|
||||||
|
getByHash,
|
||||||
|
byHeight,
|
||||||
bestHeight,
|
bestHeight,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,89 +1,51 @@
|
|||||||
const Transactions = require('../../models/transaction.js');
|
const Transactions = require('../../models/transaction.js');
|
||||||
const logger = require('../logger');
|
|
||||||
const config = require('../../config');
|
const config = require('../../config');
|
||||||
|
|
||||||
// For now, blocks handles these calls.
|
const Txs = new Transactions();
|
||||||
// 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;
|
const MAX_PAGE_TXS = config.api.max_page_txs;
|
||||||
|
|
||||||
// For Paging
|
function getEmptyInputs(cb) {
|
||||||
function getTransactions(params, options, limit, cb) {
|
return Txs.getEmptyInputs(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) {
|
function getTopTransactions(cb) {
|
||||||
// Do not return mongo ids
|
return Txs.last(cb);
|
||||||
const defaultOptions = { _id: 0 };
|
}
|
||||||
// Query mongo
|
|
||||||
Transactions.find(
|
function getTxById(txid, cb) {
|
||||||
{},
|
return Txs.byId(txid, cb);
|
||||||
(err, txs) => {
|
}
|
||||||
if (err) {
|
|
||||||
logger.log('error',
|
function getTxByBlock(blockHash, page, limit, cb) {
|
||||||
`getTransactions: ${err}`);
|
return Txs.byBlockHash(blockHash, cb)
|
||||||
return cb(err);
|
.skip(limit * page);
|
||||||
}
|
}
|
||||||
if (!txs.length > 0) {
|
|
||||||
return cb({ err: 'Tx not found' });
|
function getTxByAddress(address, page, limit, cb) {
|
||||||
}
|
return Txs.byAddress(address, cb)
|
||||||
return cb(null, txs);
|
.limit(limit)
|
||||||
})
|
.skip(limit * page);
|
||||||
.sort({ height: -1 })
|
}
|
||||||
.limit(MAX_TXS);
|
|
||||||
|
function getTxCountByBlock(blockHash, cb) {
|
||||||
|
return Txs.countByBlock(blockHash, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTxCountByAddress(address, cb) {
|
||||||
|
return Txs.countByAddress(address, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateInput(txid, inputid, value, address) {
|
||||||
|
return Txs.updateInput(txid, inputid, value, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getTransaction,
|
getEmptyInputs,
|
||||||
getTransactions,
|
|
||||||
getTopTransactions,
|
getTopTransactions,
|
||||||
|
getTxById,
|
||||||
|
getTxByBlock,
|
||||||
|
getTxCountByBlock,
|
||||||
|
getTxByAddress,
|
||||||
|
getTxCountByAddress,
|
||||||
|
updateInput,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
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 config = require('../../config');
|
const config = require('../../config');
|
||||||
const socket = require('../../lib/api/socket');
|
const socket = require('../../lib/api/socket');
|
||||||
const db = require('../../lib/db');
|
const db = require('../../lib/db');
|
||||||
@ -28,10 +28,6 @@ function start() {
|
|||||||
logger.log('error',
|
logger.log('error',
|
||||||
`${err}`);
|
`${err}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
node.mempool.on('tx', (tx) => {
|
|
||||||
socket.emitTx(tx);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
const Block = require('./block');
|
const Block = require('./block');
|
||||||
const Transaction = require('./transaction');
|
const Transaction = require('./transaction');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Block,
|
Block,
|
||||||
|
|||||||
@ -1,16 +1,10 @@
|
|||||||
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 db = require('../db');
|
const db = require('../db');
|
||||||
|
|
||||||
// Bleh, Bcoin pulls in blocks 20 at a time
|
|
||||||
// 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) => {
|
||||||
@ -58,10 +52,37 @@ function parse(entry, txs) {
|
|||||||
if (err) {
|
if (err) {
|
||||||
logger.log('error', err.message);
|
logger.log('error', err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findEmptyInputs();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findEmptyInputs() {
|
||||||
|
db.txs.getEmptyInputs(
|
||||||
|
(err, txs) => {
|
||||||
|
if (err) {
|
||||||
|
return logger.log('error',
|
||||||
|
`No Empty Inputs found: ${err.err}`);
|
||||||
|
}
|
||||||
|
// For each tx with unmarked inputs
|
||||||
|
return txs.forEach((inputTx) => {
|
||||||
|
inputTx.inputs.forEach((input) => {
|
||||||
|
const txHash = input.prevout.hash;
|
||||||
|
const outIdx = input.prevout.index;
|
||||||
|
|
||||||
|
return db.txs.getTxById(txHash, (error, tx) => {
|
||||||
|
if (error || !tx) {
|
||||||
|
return logger.log('error',
|
||||||
|
`No Tx found: ${txHash} ${error}`);
|
||||||
|
}
|
||||||
|
return db.txs.updateInput(inputTx._id, input._id, tx.outputs[outIdx].value, tx.outputs[outIdx].address);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
parse,
|
parse,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,24 +1,26 @@
|
|||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const Transaction = require('./transaction');
|
const config = require('../config');
|
||||||
|
|
||||||
const Schema = mongoose.Schema;
|
const Schema = mongoose.Schema;
|
||||||
|
// These limits can be overriden higher up the stack
|
||||||
|
const MAX_BLOCKS = config.api.max_blocks;
|
||||||
|
|
||||||
const BlockSchema = new Schema({
|
const BlockSchema = new Schema({
|
||||||
hash: { type: String, default: '' },
|
hash: { type: String, default: '' },
|
||||||
height: { type: Number, default: 0 },
|
height: { type: Number, default: 0 },
|
||||||
size: { type: Number, default: 0 },
|
size: { type: Number, default: 0 },
|
||||||
version: { type: Number, default: 0 },
|
version: { type: Number, default: 0 },
|
||||||
prevBlock: { type: String, default: '' },
|
prevBlock: { type: String, default: '' },
|
||||||
merkleRoot: { type: String, default: '' },
|
merkleRoot: { type: String, default: '' },
|
||||||
ts: { type: Number, default: 0 },
|
ts: { type: Number, default: 0 },
|
||||||
bits: { type: Number, default: 0 },
|
bits: { type: Number, default: 0 },
|
||||||
nonce: { type: Number, default: 0 },
|
nonce: { type: Number, default: 0 },
|
||||||
txs: [{ type: String, default: '' }],
|
txs: [{ type: String, default: '' }],
|
||||||
chainwork: { type: Number, default: 0 },
|
chainwork: { type: Number, default: 0 },
|
||||||
reward: { type: Number, default: 0 },
|
reward: { type: Number, default: 0 },
|
||||||
network: { type: String, default: '' },
|
network: { type: String, default: '' },
|
||||||
poolInfo: { type: Object, default: {} },
|
poolInfo: { type: Object, default: {} },
|
||||||
rawBlock: { type: String, default: '' },
|
rawBlock: { type: String, default: '' },
|
||||||
}, {
|
}, {
|
||||||
toJSON: {
|
toJSON: {
|
||||||
virtuals: true,
|
virtuals: true,
|
||||||
@ -29,6 +31,31 @@ const BlockSchema = new Schema({
|
|||||||
BlockSchema.index({ hash: 1 });
|
BlockSchema.index({ hash: 1 });
|
||||||
BlockSchema.index({ height: 1 });
|
BlockSchema.index({ height: 1 });
|
||||||
|
|
||||||
const Block = mongoose.model('Block', BlockSchema);
|
BlockSchema.methods.byHeight = function blockByHeight(height, cb) {
|
||||||
|
return this.model('Block').findOne(
|
||||||
|
{ height },
|
||||||
|
cb);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = Block;
|
BlockSchema.methods.byHash = function byHash(hash, cb) {
|
||||||
|
return this.model('Block').findOne(
|
||||||
|
{ hash },
|
||||||
|
cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockSchema.methods.getRawBlock = function getRawBlock(hash, cb) {
|
||||||
|
return this.model('Block').findOne(
|
||||||
|
{ hash },
|
||||||
|
{ rawBlock: 1 },
|
||||||
|
cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockSchema.methods.last = function lastBlocks(cb) {
|
||||||
|
return this.model('Block').find(
|
||||||
|
{},
|
||||||
|
cb)
|
||||||
|
.limit(MAX_BLOCKS)
|
||||||
|
.sort({ height: -1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Block', BlockSchema);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ const mongoose = require('mongoose');
|
|||||||
const Schema = mongoose.Schema;
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
const InputSchema = new Schema({
|
const InputSchema = new Schema({
|
||||||
|
value: { type: Number, default: 0 },
|
||||||
prevout: { type: Object, default: {} },
|
prevout: { type: Object, default: {} },
|
||||||
script: { type: String, default: '' },
|
script: { type: String, default: '' },
|
||||||
witness: { type: String, default: '' },
|
witness: { type: String, default: '' },
|
||||||
@ -10,8 +11,6 @@ const InputSchema = new Schema({
|
|||||||
address: { type: String, default: '' },
|
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;
|
||||||
|
|||||||
@ -9,8 +9,6 @@ const OutputSchema = new Schema({
|
|||||||
type: { type: String, default: '' },
|
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,29 +1,114 @@
|
|||||||
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 logger = require('../lib/logger');
|
||||||
|
const config = require('../config');
|
||||||
|
|
||||||
const Schema = mongoose.Schema;
|
const Schema = mongoose.Schema;
|
||||||
|
// These limits can be overriden higher up the stack
|
||||||
|
const MAX_TXS = config.api.max_txs;
|
||||||
|
const MAX_PAGE_TXS = config.api.max_page_txs;
|
||||||
|
|
||||||
const TransactionSchema = new Schema({
|
const TransactionSchema = new Schema({
|
||||||
hash: { type: String, default: '' },
|
hash: { type: String, default: '' },
|
||||||
witnessHash: { type: String, default: '' },
|
witnessHash: { type: String, default: '' },
|
||||||
fee: { type: Number, default: 0 },
|
fee: { type: Number, default: 0 },
|
||||||
rate: { type: Number, default: 0 },
|
rate: { type: Number, default: 0 },
|
||||||
ps: { type: Number, default: 0 },
|
ps: { type: Number, default: 0 },
|
||||||
height: { type: Number, default: 0 },
|
height: { type: Number, default: 0 },
|
||||||
block: { type: String, default: '' },
|
block: { type: String, default: '' },
|
||||||
index: { type: Number, default: 0 },
|
index: { type: Number, default: 0 },
|
||||||
version: { type: Number, default: 0 },
|
version: { type: Number, default: 0 },
|
||||||
flag: { type: Number, default: 0 },
|
flag: { type: Number, default: 0 },
|
||||||
lockTime: { type: Number, default: 0 },
|
lockTime: { type: Number, default: 0 },
|
||||||
inputs: [Input.schema],
|
inputs: [Input.schema],
|
||||||
outputs: [Output.schema],
|
outputs: [Output.schema],
|
||||||
size: { type: Number, default: 0 },
|
size: { type: Number, default: 0 },
|
||||||
network: { type: String, default: '' },
|
network: { type: String, default: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
TransactionSchema.index({ hash: 1 });
|
TransactionSchema.index({ hash: 1 });
|
||||||
|
TransactionSchema.index({ 'outputs.address': 1 });
|
||||||
|
TransactionSchema.index({ 'inputs.address': 1 });
|
||||||
|
|
||||||
const Transaction = mongoose.model('Transaction', TransactionSchema);
|
|
||||||
|
|
||||||
module.exports = Transaction;
|
TransactionSchema.methods.byId = function txById(txid, cb) {
|
||||||
|
return this.model('Transaction').findOne(
|
||||||
|
{ hash: txid },
|
||||||
|
cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionSchema.methods.byHash = function txByHash(hash, cb) {
|
||||||
|
return this.byId(hash, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionSchema.methods.byBlockHash = function txByBlockHash(hash, cb) {
|
||||||
|
return this.model('Transaction').find(
|
||||||
|
{ block: hash },
|
||||||
|
cb)
|
||||||
|
.limit(MAX_TXS);
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionSchema.methods.byAddress = function txByAddress(address, cb) {
|
||||||
|
return this.model('Transaction').find(
|
||||||
|
{
|
||||||
|
$or: [
|
||||||
|
{ 'inputs.address': address },
|
||||||
|
{ 'outputs.address': address }],
|
||||||
|
},
|
||||||
|
cb)
|
||||||
|
.limit(MAX_TXS);
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionSchema.methods.countByBlock = function txByAddress(hash, cb) {
|
||||||
|
return this.model('Transaction').count(
|
||||||
|
{ block: hash },
|
||||||
|
cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionSchema.methods.countByAddress = function txByAddress(address, cb) {
|
||||||
|
return this.model('Transaction').count(
|
||||||
|
{
|
||||||
|
$or: [
|
||||||
|
{ 'inputs.address': address },
|
||||||
|
{ 'outputs.address': address }],
|
||||||
|
},
|
||||||
|
cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionSchema.methods.last = function lastTx(cb) {
|
||||||
|
return this.model('Transaction').find(
|
||||||
|
{},
|
||||||
|
cb)
|
||||||
|
.limit(MAX_TXS)
|
||||||
|
.sort({ height: -1 });
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionSchema.methods.getEmptyInputs = function getEmptyInputs(cb) {
|
||||||
|
return this.model('Transaction').find({
|
||||||
|
'inputs.prevout.hash': { $ne: '0000000000000000000000000000000000000000000000000000000000000000' },
|
||||||
|
'inputs.address': '',
|
||||||
|
},
|
||||||
|
cb)
|
||||||
|
.limit(MAX_TXS);
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionSchema.methods.updateInput = function updateInput(txid, inputid, value, address) {
|
||||||
|
return this.model('Transaction').findOneAndUpdate(
|
||||||
|
{ _id: txid, 'inputs._id': inputid },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
'inputs.$.value': value,
|
||||||
|
'inputs.$.address': address,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(err, tx) => {
|
||||||
|
if (err) {
|
||||||
|
logger.log('error',
|
||||||
|
`updateInput: ${err}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Transaction', TransactionSchema);
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
"bitcore-message": "^1.0.4",
|
"bitcore-message": "^1.0.4",
|
||||||
"body-parser": "^1.17.2",
|
"body-parser": "^1.17.2",
|
||||||
"express": "^4.15.3",
|
"express": "^4.15.3",
|
||||||
|
"helmet": "^3.8.1",
|
||||||
"mongoose": "^4.11.5",
|
"mongoose": "^4.11.5",
|
||||||
"request": "^2.81.0",
|
"request": "^2.81.0",
|
||||||
"socket.io": "^2.0.3",
|
"socket.io": "^2.0.3",
|
||||||
|
|||||||
BIN
server/public/img/bcoin.png
Normal file
BIN
server/public/img/bcoin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Loading…
Reference in New Issue
Block a user