From 4ef0949061bba91e32ed53b48ec694dbe7b77169 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Thu, 10 Aug 2017 23:39:08 -0400 Subject: [PATCH 01/44] returning data --- server/lib/api/address.js | 82 ++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/server/lib/api/address.js b/server/lib/api/address.js index 2b77eeb..5a147b8 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -3,41 +3,105 @@ const logger = require('../logger'); const MAX_BLOCKS = 200; +// Shoe horned in. Not dry, also in blocks. Make db api later +function getBlock(params, options, limit, cb) { + const defaultOptions = { _id: 0 }; + + Object.assign(defaultOptions, options); + + console.log + + Block.find( + params, + defaultOptions, + cb) + .sort({ height: -1 }) + .limit(limit); +} + module.exports = function AddressAPI(router) { router.get('/addr/:addr', (req, res) => { - res.send(req.params.addr); + getBlock( + { + $or: + [ + { 'txs.outputs.address': req.params.addr }, + { 'txs.inputs.prevout.hash': req.params.addr }, + ], + }, + { rawBlock: 0 }, + MAX_BLOCKS, + (err, block) => { + if (err) { + res.status(501).send(); + logger.log('err', err); + } + + if (block[0]) { + const b = block[0]; + res.json({ + pagesTotal: 1, + txs: b.txs.map(tx => ({ + txid: tx.hash, + version: tx.version, + locktime: tx.locktime, + vin: tx.inputs.map(input => ({ + coinbase: input.script, + sequence: input.sequence, + n: 0, + addr: input.address, + })), + vout: tx.outputs.map(output => ({ + value: output.value / 1e8, + n: 0, + scriptPubKey: { + hex: '', + asm: '', + addresses: [output.address], + type: output.type, + }, + spentTxid: '', + spentIndex: 0, + spentHeight: 0, + })), + })), + }); + } else { + res.send(); + } + }); }); router.get('/addr/:addr/utxo', (req, res) => { - res.send(req.params.addr); + res.send("1"); }); router.get('/addr/:addr/balance', (req, res) => { - res.send(req.params.addr); + res.send("2"); }); router.get('/addr/:addr/totalReceived', (req, res) => { - res.send(req.params.addr); + res.send("3"); }); router.get('/addr/:addr/totalSent', (req, res) => { - res.send(req.params.addr); + res.send("4"); }); router.get('/addr/:addr/unconfirmedBalance', (req, res) => { - res.send(req.params.addr); + res.send("5"); }); router.get('/addrs/:addrs/utxo', (req, res) => { - res.send(req.params.addrs); + res.send("6"); }); router.post('/addrs/utxo', (req, res) => { - res.send('post stub'); + res.send("7"); }); router.get('/addrs/:addrs/txs', (req, res) => { - res.send(req.params.addrs); + res.send("8"); }); router.post('/addrs/txs', (req, res) => { From 741a234c76e22221254e75e740373b325908278a Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Thu, 10 Aug 2017 23:40:38 -0400 Subject: [PATCH 02/44] Removing saving from tx parser. Only use for emitting block txs for now. Increase tx socket delay --- server/lib/parser/transaction.js | 44 +++----------------------------- 1 file changed, 3 insertions(+), 41 deletions(-) diff --git a/server/lib/parser/transaction.js b/server/lib/parser/transaction.js index 9bb4fa3..8549ffe 100644 --- a/server/lib/parser/transaction.js +++ b/server/lib/parser/transaction.js @@ -6,7 +6,7 @@ const util = require('../../lib/util'); const logger = require('../logger'); const io = require('../api').io; -const socketThrottle = 100; +const socketThrottle = 250; let counter = 0; function parse(entry, txs) { @@ -17,6 +17,8 @@ function parse(entry, txs) { if (counter % socketThrottle === 0) { + counter = 0; + io.sockets.emit('tx', { txid: txJSON.hash, valueOut: tx.outputs.reduce((sum, tx) => { @@ -28,46 +30,6 @@ function parse(entry, txs) { }, 0), }); } - - const t = new TxModel({ - 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, - }); - t.save((err) => { - if (err) { - logger.log('error', err.message); - } - }); }); } From e27c7bcf871e66bf768a8a7ad368ba3173d12700 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Fri, 11 Aug 2017 00:18:03 -0400 Subject: [PATCH 03/44] Address page working. Address details stubbed --- server/lib/api/address.js | 84 ++++++++++++++++++++--------------- server/lib/api/transaction.js | 61 ++++++++++++++++++++++--- 2 files changed, 103 insertions(+), 42 deletions(-) diff --git a/server/lib/api/address.js b/server/lib/api/address.js index 5a147b8..21b7ccd 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -7,9 +7,11 @@ const MAX_BLOCKS = 200; function getBlock(params, options, limit, cb) { const defaultOptions = { _id: 0 }; - Object.assign(defaultOptions, options); + if (!Number.isInteger(limit)) { + limit = MAX_BLOCKS; + } - console.log + Object.assign(defaultOptions, options); Block.find( params, @@ -21,11 +23,55 @@ function getBlock(params, options, limit, cb) { module.exports = function AddressAPI(router) { router.get('/addr/:addr', (req, res) => { + res.json({ + addrStr: req.params.addr, + balance: 0, + balanceSat: 0, + totalReceived: 0, + totalReceivedSat: 0, + totalSent: 0, + totalSentSat: 0, + unconfirmedBalance: 0, + unconfirmedBalanceSat: 0, + unconfirmedTxApperances: 0, + txApperances: 5, + }); + }); + + router.get('/addr/:addr/utxo', (req, res) => { + res.send('1'); + }); + + router.get('/addr/:addr/balance', (req, res) => { + res.send('2'); + }); + + router.get('/addr/:addr/totalReceived', (req, res) => { + res.send('3'); + }); + + router.get('/addr/:addr/totalSent', (req, res) => { + res.send('4'); + }); + + router.get('/addr/:addr/unconfirmedBalance', (req, res) => { + res.send('5'); + }); + + router.get('/addrs/:addrs/utxo', (req, res) => { + res.send('6'); + }); + + router.post('/addrs/utxo', (req, res) => { + res.send('7'); + }); + + router.get('/addrs/:addrs/txs', (req, res) => { getBlock( { $or: [ - { 'txs.outputs.address': req.params.addr }, + { 'txs.outputs.address': req.params.addr }, { 'txs.inputs.prevout.hash': req.params.addr }, ], }, @@ -72,38 +118,6 @@ module.exports = function AddressAPI(router) { }); }); - router.get('/addr/:addr/utxo', (req, res) => { - res.send("1"); - }); - - router.get('/addr/:addr/balance', (req, res) => { - res.send("2"); - }); - - router.get('/addr/:addr/totalReceived', (req, res) => { - res.send("3"); - }); - - router.get('/addr/:addr/totalSent', (req, res) => { - res.send("4"); - }); - - router.get('/addr/:addr/unconfirmedBalance', (req, res) => { - res.send("5"); - }); - - router.get('/addrs/:addrs/utxo', (req, res) => { - res.send("6"); - }); - - router.post('/addrs/utxo', (req, res) => { - res.send("7"); - }); - - router.get('/addrs/:addrs/txs', (req, res) => { - res.send("8"); - }); - router.post('/addrs/txs', (req, res) => { res.send('post stub'); }); diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 15ac88a..e83eb93 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -6,9 +6,13 @@ const MAX_TXS = 20; const MAX_BLOCKS = 1; // Shoe horned in. Not dry, also in blocks. Make db api later -function getBlock(params, options, cb) { +function getBlock(params, options, limit, cb) { const defaultOptions = { _id: 0 }; + if (!Number.isInteger(limit)) { + limit = MAX_BLOCKS; + } + Object.assign(defaultOptions, options); Block.find( @@ -16,7 +20,7 @@ function getBlock(params, options, cb) { defaultOptions, cb) .sort({ height: -1 }) - .limit(MAX_BLOCKS); + .limit(limit); } @@ -85,15 +89,11 @@ module.exports = function transactionAPI(router) { }); router.get('/txs', (req, res) => { -/* - const txsBy = req.query.blocks || - req.query.address; -*/ - if (req.query.block) { getBlock( { hash: req.query.block }, { rawBlock: 0 }, + MAX_BLOCKS, (err, block) => { if (err) { res.status(501).send(); @@ -133,7 +133,54 @@ module.exports = function transactionAPI(router) { } }); } else if (req.query.address) { + getBlock( + { $or: + [ + { 'txs.outputs.address': req.query.address }, + { 'txs.inputs.prevout.hash': req.query.address }, + ], + }, + { rawBlock: 0 }, + MAX_BLOCKS, + (err, block) => { + if (err) { + res.status(501).send(); + logger.log('err', err); + } + if (block[0]) { + const b = block[0]; + res.json({ + pagesTotal: 1, + txs: b.txs.map(tx => ({ + txid: tx.hash, + version: tx.version, + locktime: tx.locktime, + vin: tx.inputs.map(input => ({ + coinbase: input.script, + sequence: input.sequence, + n: 0, + addr: input.address, + })), + vout: tx.outputs.map(output => ({ + value: output.value / 1e8, + n: 0, + scriptPubKey: { + hex: '', + asm: '', + addresses: [output.address], + type: output.type, + }, + spentTxid: '', + spentIndex: 0, + spentHeight: 0, + })), + })), + }); + } else { + res.send(); + } + }); } else { getTransactions( {}, From ba1333f84f3596182d3a7ca2e94e88fcb07364cc Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Mon, 14 Aug 2017 02:02:28 -0400 Subject: [PATCH 04/44] PR review catches and cleanups --- server/lib/api/transaction.js | 4 +--- server/lib/parser/address.js | 17 ----------------- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index e83eb93..0e93b43 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -146,9 +146,7 @@ module.exports = function transactionAPI(router) { if (err) { res.status(501).send(); logger.log('err', err); - } - - if (block[0]) { + } else if (block[0]) { const b = block[0]; res.json({ pagesTotal: 1, diff --git a/server/lib/parser/address.js b/server/lib/parser/address.js index 8d4955e..f53c35c 100644 --- a/server/lib/parser/address.js +++ b/server/lib/parser/address.js @@ -7,30 +7,13 @@ 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, - }); - })*/ }); }); } From 9045eacccd46f5e80eff3289c4b9981186f5f51f Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Mon, 14 Aug 2017 17:58:12 -0400 Subject: [PATCH 05/44] Fixed txs per request to only save block w/ subdocs --- server/config/index.js | 2 +- server/lib/api/transaction.js | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/server/config/index.js b/server/config/index.js index d3bc4dc..66878ce 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -1,5 +1,5 @@ const config = { - start_node: true, + start_node: false, logging: 'debug', bcoin: { network: 'main', diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 0e93b43..6fd7b1c 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -39,17 +39,22 @@ function getTransactions(params, options, cb) { module.exports = function transactionAPI(router) { router.get('/tx/:txid', (req, res) => { - getTransactions( - { hash: req.params.txid }, + getBlock( + { 'txs.hash': req.params.txid }, { }, - (err, tx) => { + MAX_BLOCKS, + (err, blocks) => { if (err) { res.status(501).send(); logger.log('err', err); } - if (tx[0]) { - const t = tx[0]; + if (blocks[0] && blocks[0].txs) { + let t = blocks[0].txs.filter(tx => tx.hash === req.params.txid); + t = t[0]; + console.log(t); + console.log(t.inputs); + console.log(t.inputs); // Map bcoin model to insight-api res.json({ txid: t.hash, From 90adbf3ece750550ebfaea79bb7dff6c03d59c84 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Mon, 14 Aug 2017 22:10:00 -0400 Subject: [PATCH 06/44] remove Block from status --- server/lib/api/status.js | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/server/lib/api/status.js b/server/lib/api/status.js index 12fd96d..78d2266 100644 --- a/server/lib/api/status.js +++ b/server/lib/api/status.js @@ -1,28 +1,7 @@ -const Block = require('../../models/block.js'); const pkg = require('../../package.json'); const logger = require('../logger'); -const MAX_BLOCKS = 200; - -// Not dry, in multiple APIs. Refactor to db api -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 statusAPI(router) { router.get('/status', (req, res) => { res.json({ From b1e540c8df91fa7c2b2a3c5a21dd7786273a25df Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 02:00:04 -0400 Subject: [PATCH 07/44] RPC hookups fill the gaps way better than mongo in some places. See changes --- server/config/index.js | 6 +- server/lib/api/block.js | 3 +- server/lib/api/transaction.js | 231 ++++++++++++++-------------------- server/models/block.js | 3 + server/models/transaction.js | 2 + 5 files changed, 105 insertions(+), 140 deletions(-) diff --git a/server/config/index.js b/server/config/index.js index 66878ce..0467c00 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -1,15 +1,17 @@ const config = { - start_node: false, + start_node: true, logging: 'debug', bcoin: { network: 'main', db: 'leveldb', prefix: '.', checkpoints: true, - workers: true, + workers: false, logLevel: 'info', 'max-inbound': 10, 'max-outbound': 10, + 'index-tx': true, + 'index-address': true, }, mongodb: { uri: 'mongodb://localhost/bitcore', diff --git a/server/lib/api/block.js b/server/lib/api/block.js index cbc1c01..36e0b53 100644 --- a/server/lib/api/block.js +++ b/server/lib/api/block.js @@ -1,7 +1,7 @@ const Block = require('../../models/block.js'); const logger = require('../logger'); -const MAX_BLOCKS = 200; +const MAX_BLOCKS = 100; function getBlock(params, options, limit, cb) { const defaultOptions = { _id: 0 }; @@ -76,6 +76,7 @@ module.exports = function BlockAPI(router) { res.status(501).send(); logger.log('err', err); } + res.json({ blocks: blocks.map(block => ({ hash: block.hash, diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 6fd7b1c..2154f7b 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -1,6 +1,7 @@ const Block = require('../../models/block.js'); const Transaction = require('../../models/transaction'); const logger = require('../logger'); +const request = require('request'); const MAX_TXS = 20; const MAX_BLOCKS = 1; @@ -39,151 +40,107 @@ function getTransactions(params, options, cb) { module.exports = function transactionAPI(router) { router.get('/tx/:txid', (req, res) => { - getBlock( - { 'txs.hash': req.params.txid }, - { }, - MAX_BLOCKS, - (err, blocks) => { - if (err) { - res.status(501).send(); - logger.log('err', err); - } - - if (blocks[0] && blocks[0].txs) { - let t = blocks[0].txs.filter(tx => tx.hash === req.params.txid); - t = t[0]; - console.log(t); - console.log(t.inputs); - console.log(t.inputs); - // Map bcoin model to insight-api - res.json({ - txid: t.hash, - version: t.version, - locktime: t.lockTime, - vin: t.inputs.map(input => ({ - coinbase: input.script, - sequence: input.sequence, - n: 0, - })), - 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(); - } + request(`http://localhost:8332/tx/${req.params.txid}`, (err, localRes, body) => { + if (err) { + logger.log('error', + `${err}`); + } + try { + body = JSON.parse(body); + } catch (e) { + logger.log('error', + `${err}`); + } + res.send({ + txid: body.hash, + version: body.version, + time: body.ps, + blocktime: body.ps, + locktime: body.locktime, + blockhash: body.block, + fees: body.fee, + valueOut: body.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, + vin: body.inputs.map(input => ({ + addr: input.coin ? input.coin.address : '', + value: input.coin ? input.coin.value / 1e8 : 0, + })), + vout: body.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + isCoinbase: body.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', }); + }); }); router.get('/txs', (req, res) => { if (req.query.block) { - getBlock( - { hash: req.query.block }, - { rawBlock: 0 }, - MAX_BLOCKS, - (err, block) => { - if (err) { - res.status(501).send(); - logger.log('err', err); - } - if (block[0]) { - const b = block[0]; - res.json({ - pagesTotal: 1, - txs: b.txs.map(tx => ({ - txid: tx.hash, - version: tx.version, - locktime: tx.locktime, - vin: tx.inputs.map(input => ({ - coinbase: input.script, - sequence: input.sequence, - n: 0, - addr: input.address, - })), - vout: tx.outputs.map(output => ({ - value: output.value / 1e8, - n: 0, - scriptPubKey: { - hex: '', - asm: '', - addresses: [output.address], - type: output.type, - }, - spentTxid: '', - spentIndex: 0, - spentHeight: 0, - })), - })), - }); - } else { - res.send(); - } + request(`http://localhost:8332/block/${req.query.block}`, (err, localRes, body) => { + if (err) { + logger.log('error', + `${err}`); + } + try { + body = JSON.parse(body); + } catch (e) { + logger.log('error', + `${err}`); + } + res.send({ + pagesTotal: 1, + txs: body.txs.map(tx => ({ + txid: tx.hash, + fees: tx.fee, + 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, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + output: tx.outputs, + })), }); + }); } else if (req.query.address) { - getBlock( - { $or: - [ - { 'txs.outputs.address': req.query.address }, - { 'txs.inputs.prevout.hash': req.query.address }, - ], - }, - { rawBlock: 0 }, - MAX_BLOCKS, - (err, block) => { - if (err) { - res.status(501).send(); - logger.log('err', err); - } else if (block[0]) { - const b = block[0]; - res.json({ - pagesTotal: 1, - txs: b.txs.map(tx => ({ - txid: tx.hash, - version: tx.version, - locktime: tx.locktime, - vin: tx.inputs.map(input => ({ - coinbase: input.script, - sequence: input.sequence, - n: 0, - addr: input.address, - })), - vout: tx.outputs.map(output => ({ - value: output.value / 1e8, - n: 0, - scriptPubKey: { - hex: '', - asm: '', - addresses: [output.address], - type: output.type, - }, - spentTxid: '', - spentIndex: 0, - spentHeight: 0, - })), - })), - }); - } else { - res.send(); - } + request(`http://localhost:8332/tx/address/${req.query.address}`, (err, localRes, body) => { + if (err) { + logger.log('error', + `${err}`); + } + try { + body = JSON.parse(body); + } catch (e) { + logger.log('error', + `${err}`); + } + console.log(body); + res.send({ + pagesTotal: 1, + txs: body.map(tx => ({ + txid: tx.hash, + fees: tx.fee, + 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, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + output: tx.outputs, + })), }); + }); } else { getTransactions( {}, diff --git a/server/models/block.js b/server/models/block.js index 3de6806..515fe70 100644 --- a/server/models/block.js +++ b/server/models/block.js @@ -26,6 +26,9 @@ const BlockSchema = new Schema({ id: false, }); +BlockSchema.index({ hash: 1 }); +BlockSchema.index({ height: 1 }); + const Block = mongoose.model('Block', BlockSchema); module.exports = Block; diff --git a/server/models/transaction.js b/server/models/transaction.js index 73c4f4d..ec0af09 100644 --- a/server/models/transaction.js +++ b/server/models/transaction.js @@ -22,6 +22,8 @@ const TransactionSchema = new Schema({ network: String, }); +TransactionSchema.index({ hash: 1 }); + const Transaction = mongoose.model('Transaction', TransactionSchema); module.exports = Transaction; From 4b71ae91ae0f4dcd819e6d35ef3e278cc6e60883 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 02:24:30 -0400 Subject: [PATCH 08/44] moved rpc settings to config. Little confusing as is. Needs improvement --- server/config/index.js | 2 ++ server/lib/api/transaction.js | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server/config/index.js b/server/config/index.js index 0467c00..714f954 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -1,6 +1,7 @@ const config = { start_node: true, logging: 'debug', + bcoin_http: 'localhost', bcoin: { network: 'main', db: 'leveldb', @@ -12,6 +13,7 @@ const config = { 'max-outbound': 10, 'index-tx': true, 'index-address': true, + 'http-port': 8332, }, mongodb: { uri: 'mongodb://localhost/bitcore', diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 2154f7b..eb89130 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -2,6 +2,7 @@ const Block = require('../../models/block.js'); const Transaction = require('../../models/transaction'); const logger = require('../logger'); const request = require('request'); +const config = require('../../config'); const MAX_TXS = 20; const MAX_BLOCKS = 1; @@ -40,7 +41,7 @@ function getTransactions(params, options, cb) { module.exports = function transactionAPI(router) { router.get('/tx/:txid', (req, res) => { - request(`http://localhost:8332/tx/${req.params.txid}`, (err, localRes, body) => { + request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/tx/${req.params.txid}`, (err, localRes, body) => { if (err) { logger.log('error', `${err}`); @@ -77,7 +78,7 @@ module.exports = function transactionAPI(router) { router.get('/txs', (req, res) => { if (req.query.block) { - request(`http://localhost:8332/block/${req.query.block}`, (err, localRes, body) => { + request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/block/${req.query.block}`, (err, localRes, body) => { if (err) { logger.log('error', `${err}`); @@ -109,7 +110,7 @@ module.exports = function transactionAPI(router) { }); }); } else if (req.query.address) { - request(`http://localhost:8332/tx/address/${req.query.address}`, (err, localRes, body) => { + request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/tx/address/${req.query.address}`, (err, localRes, body) => { if (err) { logger.log('error', `${err}`); @@ -120,7 +121,6 @@ module.exports = function transactionAPI(router) { logger.log('error', `${err}`); } - console.log(body); res.send({ pagesTotal: 1, txs: body.map(tx => ({ From 69538704f6380ae5a3de1f403489625f17c563f7 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 03:12:23 -0400 Subject: [PATCH 09/44] sync, status and last block hash finished --- server/lib/api/block.js | 1 - server/lib/api/index.js | 2 + server/lib/api/status.js | 99 ++++++++++++++++++++++++++++++---------- 3 files changed, 78 insertions(+), 24 deletions(-) diff --git a/server/lib/api/block.js b/server/lib/api/block.js index 36e0b53..c7c9dd6 100644 --- a/server/lib/api/block.js +++ b/server/lib/api/block.js @@ -116,7 +116,6 @@ module.exports = function BlockAPI(router) { res.status(501).send(); logger.log('err', err); } - if (block[0]) { res.json({ blockHash: block[0].hash, diff --git a/server/lib/api/index.js b/server/lib/api/index.js index f97e539..0d4610e 100644 --- a/server/lib/api/index.js +++ b/server/lib/api/index.js @@ -10,10 +10,12 @@ app.use(cors); // Serve insight ui front end from root dir public folder app.use('/', express.static('./public')); app.use('/blocks', express.static('./public')); +app.use('/block-index', 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.set('json spaces', config.api.json_spaces); diff --git a/server/lib/api/status.js b/server/lib/api/status.js index 78d2266..758e55a 100644 --- a/server/lib/api/status.js +++ b/server/lib/api/status.js @@ -1,34 +1,87 @@ +const request = require('request'); +const Block = require('../../models/block'); const pkg = require('../../package.json'); - +const config = require('../../config'); +const netCfg = require('bcoin/lib/net/common'); const logger = require('../logger'); +// Here comes the ugly. Moo who haha +function getStatus(cb) { + request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/`, (err, localRes, body) => { + if (err) { + logger.log('error', + `${err}`); + } + try { + body = JSON.parse(body); + } catch (e) { + logger.log('error', + `${err}`); + cb(e); + } + cb(null, body); + }); +} + module.exports = function statusAPI(router) { router.get('/status', (req, res) => { - res.json({ - info: { - version: 0, - protocolversion: 0, - blocks: 0, - timeoffset: 0, - connections: 0, - proxy: '', - difficulty: 0, - testnet: false, - relayfee: 0, - errors: '', - network: 'main', - }, - }); + if (req.query.q === 'getLastBlockHash') { + Block.findOne({}, { 'hash': 1 }, { sort: { 'height': -1 } }, (err, block) => { + if (err) { + logger.log('error', + `${err}`); + res.status(501).send(err); + } else { + res.send({ + syncTipHash: block.hash, + lastblockhash: block.hash, + }); + } + }); + } else { + getStatus((err, status) => { + if (err) { + res.status(501).send(err); + } else if (status) { + 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, + }, + }); + } else { + res.send(); + } + }); + } + }); router.get('/sync', (req, res) => { - res.json({ - status: '', - blockChainHeight: 0, - syncPercentage: 0, - height: 0, - error: null, - type: 'bitcore node', + getStatus((err, status) => { + if (err) { + res.status(501).send(err); + } else if (status) { + res.json({ + status: 'syncing', + blockChainHeight: status.chain.height, + syncPercentage: Math.round(status.chain.progress * 100), + height: status.chain.height, + error: null, + type: 'bcoin node', + }); + } else { + res.send(); + } }); }); From dd0e0bedf08c9da5287088f385207b360fcb2e52 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 03:19:21 -0400 Subject: [PATCH 10/44] update tx fees div 1e8 --- server/lib/api/transaction.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index eb89130..05036bf 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -59,7 +59,7 @@ module.exports = function transactionAPI(router) { blocktime: body.ps, locktime: body.locktime, blockhash: body.block, - fees: body.fee, + fees: body.fee / 1e8, valueOut: body.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, vin: body.inputs.map(input => ({ addr: input.coin ? input.coin.address : '', @@ -93,7 +93,7 @@ module.exports = function transactionAPI(router) { pagesTotal: 1, txs: body.txs.map(tx => ({ txid: tx.hash, - fees: tx.fee, + fees: tx.fee / 1e8, valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, vin: tx.inputs.map(input => ({ addr: input.coin ? input.coin.address : '', @@ -125,7 +125,7 @@ module.exports = function transactionAPI(router) { pagesTotal: 1, txs: body.map(tx => ({ txid: tx.hash, - fees: tx.fee, + fees: tx.fee / 1e8, valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, vin: tx.inputs.map(input => ({ addr: input.coin ? input.coin.address : '', From 2b8eee6e03cab63e38475a8724ab36d26ee8f6e7 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 04:09:28 -0400 Subject: [PATCH 11/44] Confirmations wired up for blocks txs and addr views --- server/lib/api/transaction.js | 227 ++++++++++++++++++++-------------- 1 file changed, 135 insertions(+), 92 deletions(-) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 05036bf..83ce1e8 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -41,106 +41,149 @@ function getTransactions(params, options, cb) { module.exports = function transactionAPI(router) { router.get('/tx/:txid', (req, res) => { - request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/tx/${req.params.txid}`, (err, localRes, body) => { - if (err) { - logger.log('error', - `${err}`); - } - try { - body = JSON.parse(body); - } catch (e) { - logger.log('error', - `${err}`); - } - res.send({ - txid: body.hash, - version: body.version, - time: body.ps, - blocktime: body.ps, - locktime: body.locktime, - blockhash: body.block, - fees: body.fee / 1e8, - valueOut: body.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, - vin: body.inputs.map(input => ({ - addr: input.coin ? input.coin.address : '', - value: input.coin ? input.coin.value / 1e8 : 0, - })), - vout: body.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - isCoinbase: body.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', + getBlock( + {}, + { height: 1 }, + 1, + (err, block) => { + if (err) { + res.status(501).send(); + logger.log('err', err); + } + if (block[0]) { + const height = block[0].height; + request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/tx/${req.params.txid}`, (err, localRes, body) => { + if (err) { + logger.log('error', + `${err}`); + } + try { + body = JSON.parse(body); + } catch (e) { + logger.log('error', + `${err}`); + } + res.send({ + txid: body.hash, + version: body.version, + time: body.ps, + blocktime: body.ps, + locktime: body.locktime, + blockhash: body.block, + fees: body.fee / 1e8, + confirmations: height - body.height, + valueOut: body.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, + vin: body.inputs.map(input => ({ + addr: input.coin ? input.coin.address : '', + value: input.coin ? input.coin.value / 1e8 : 0, + })), + vout: body.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + isCoinbase: body.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', + }); + }); + } }); - }); }); + // That callback hell router.get('/txs', (req, res) => { if (req.query.block) { - request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/block/${req.query.block}`, (err, localRes, body) => { - if (err) { - logger.log('error', - `${err}`); - } - try { - body = JSON.parse(body); - } catch (e) { - logger.log('error', - `${err}`); - } - res.send({ - pagesTotal: 1, - txs: body.txs.map(tx => ({ - txid: tx.hash, - fees: tx.fee / 1e8, - 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, - })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - output: tx.outputs, - })), + getBlock( + {}, + { height: 1 }, + 1, + (err, block) => { + if (err) { + res.status(501).send(); + logger.log('err', err); + } + if (block[0]) { + const height = block[0].height; + request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/block/${req.query.block}`, (err, localRes, body) => { + if (err) { + logger.log('error', + `${err}`); + } + try { + body = JSON.parse(body); + } catch (e) { + logger.log('error', + `${err}`); + } + res.send({ + pagesTotal: 1, + txs: body.txs.map(tx => ({ + txid: tx.hash, + fees: tx.fee / 1e8, + confirmations: height - body.height, + 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, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + output: tx.outputs, + })), + }); + }); + } }); - }); } else if (req.query.address) { - request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/tx/address/${req.query.address}`, (err, localRes, body) => { - if (err) { - logger.log('error', - `${err}`); - } - try { - body = JSON.parse(body); - } catch (e) { - logger.log('error', - `${err}`); - } - res.send({ - pagesTotal: 1, - txs: body.map(tx => ({ - txid: tx.hash, - fees: tx.fee / 1e8, - 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, - })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - output: tx.outputs, - })), + getBlock( + {}, + { height: 1 }, + 1, + (err, block) => { + if (err) { + res.status(501).send(); + logger.log('err', err); + } + if (block[0]) { + const height = block[0].height; + request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/tx/address/${req.query.address}`, (err, localRes, body) => { + if (err) { + logger.log('error', + `${err}`); + } + try { + body = JSON.parse(body); + } catch (e) { + logger.log('error', + `${err}`); + } + res.send({ + pagesTotal: 1, + txs: body.map(tx => ({ + txid: tx.hash, + fees: tx.fee / 1e8, + confirmations: height - tx.height, + 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, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + output: tx.outputs, + })), + }); + }); + } }); - }); } else { getTransactions( {}, From 17acbdda6d5cf8b6547abfd0d59497499fbb7351 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 04:11:22 -0400 Subject: [PATCH 12/44] messiest yet, bump confs by 1 --- server/lib/api/transaction.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 83ce1e8..d1f72e9 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -71,7 +71,7 @@ module.exports = function transactionAPI(router) { locktime: body.locktime, blockhash: body.block, fees: body.fee / 1e8, - confirmations: height - body.height, + confirmations: height - body.height + 1, valueOut: body.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, vin: body.inputs.map(input => ({ addr: input.coin ? input.coin.address : '', @@ -120,7 +120,7 @@ module.exports = function transactionAPI(router) { txs: body.txs.map(tx => ({ txid: tx.hash, fees: tx.fee / 1e8, - confirmations: height - body.height, + confirmations: height - body.height + 1, valueOut: tx.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, vin: tx.inputs.map(input => ({ addr: input.coin ? input.coin.address : '', @@ -166,7 +166,7 @@ module.exports = function transactionAPI(router) { txs: body.map(tx => ({ txid: tx.hash, fees: tx.fee / 1e8, - confirmations: height - tx.height, + 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 : '', From a3e4e19a9d8f6a13480eb197e9689ed9e6799c6b Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 04:37:53 -0400 Subject: [PATCH 13/44] address summary finished --- server/lib/api/address.js | 69 +++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/server/lib/api/address.js b/server/lib/api/address.js index 21b7ccd..ba56875 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -1,5 +1,7 @@ const Block = require('../../models/block.js'); const logger = require('../logger'); +const request = require('request'); +const config = require('../../config'); const MAX_BLOCKS = 200; @@ -23,19 +25,60 @@ function getBlock(params, options, limit, cb) { module.exports = function AddressAPI(router) { router.get('/addr/:addr', (req, res) => { - res.json({ - addrStr: req.params.addr, - balance: 0, - balanceSat: 0, - totalReceived: 0, - totalReceivedSat: 0, - totalSent: 0, - totalSentSat: 0, - unconfirmedBalance: 0, - unconfirmedBalanceSat: 0, - unconfirmedTxApperances: 0, - txApperances: 5, - }); + getBlock( + {}, + { height: 1 }, + 1, + (err, block) => { + if (err) { + res.status(501).send(); + logger.log('err', err); + } + if (block[0]) { + const height = block[0].height; + request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/tx/address/${req.params.addr}`, (err, localRes, body) => { + if (err) { + logger.log('error', + `${err}`); + } + try { + body = JSON.parse(body); + } catch (e) { + logger.log('error', + `${err}`); + } + const totalSpent = body.reduce((sum, tx) => sum + tx.outputs.reduce((sum, output) => { + if (output.address === req.params.addr) { + return sum + output.value; + } + return sum; + }, 0), 0); + + const totalReceived = body.reduce((sum, tx) => sum + tx.inputs.reduce((sum, input) => { + if (input.coin && input.coin.address === req.params.addr) { + return sum + input.coin.value; + } + return sum; + }, 0), 0); + + + + res.json({ + addrStr: req.params.addr, + balance: totalReceived - totalSpent, + balanceSat: totalReceived - totalSpent, + totalReceived: totalReceived / 1e8, + totalReceivedSat: totalReceived, + totalSent: totalSpent / 1e8, + totalSentSat: totalSpent, + unconfirmedBalance: 0, + unconfirmedBalanceSat: 0, + unconfirmedTxApperances: 0, + txApperances: body.length, + }); + }); + } + }); }); router.get('/addr/:addr/utxo', (req, res) => { From bf26304746dd3ad9ad0a60b8f167bd32fea5a93f Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 10:42:36 -0400 Subject: [PATCH 14/44] Remove index from txs. Explicit return and fix spent and received in addr. --- server/lib/api/address.js | 4 ++-- server/lib/api/block.js | 2 +- server/models/transaction.js | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/server/lib/api/address.js b/server/lib/api/address.js index ba56875..c12376a 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -47,14 +47,14 @@ module.exports = function AddressAPI(router) { logger.log('error', `${err}`); } - const totalSpent = body.reduce((sum, tx) => sum + tx.outputs.reduce((sum, output) => { + const totalReceived = body.reduce((sum, tx) => sum + tx.outputs.reduce((sum, output) => { if (output.address === req.params.addr) { return sum + output.value; } return sum; }, 0), 0); - const totalReceived = body.reduce((sum, tx) => sum + tx.inputs.reduce((sum, input) => { + const totalSpent = body.reduce((sum, tx) => sum + tx.inputs.reduce((sum, input) => { if (input.coin && input.coin.address === req.params.addr) { return sum + input.coin.value; } diff --git a/server/lib/api/block.js b/server/lib/api/block.js index c7c9dd6..2ff49ed 100644 --- a/server/lib/api/block.js +++ b/server/lib/api/block.js @@ -60,7 +60,6 @@ module.exports = function BlockAPI(router) { router.get('/blocks', (req, res) => { const limit = parseInt(req.query.limit) || MAX_BLOCKS; - getBlock( {}, { height: 1, @@ -75,6 +74,7 @@ module.exports = function BlockAPI(router) { if (err) { res.status(501).send(); logger.log('err', err); + return; } res.json({ diff --git a/server/models/transaction.js b/server/models/transaction.js index ec0af09..73c4f4d 100644 --- a/server/models/transaction.js +++ b/server/models/transaction.js @@ -22,8 +22,6 @@ const TransactionSchema = new Schema({ network: String, }); -TransactionSchema.index({ hash: 1 }); - const Transaction = mongoose.model('Transaction', TransactionSchema); module.exports = Transaction; From 6659148d4bb20227e29ab8cc37eb4b760005b4bc Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 10:59:40 -0400 Subject: [PATCH 15/44] fix bad txid --- server/lib/api/transaction.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index d1f72e9..397537f 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -62,6 +62,14 @@ module.exports = function transactionAPI(router) { } catch (e) { logger.log('error', `${err}`); + res.status(501).send(); + return; + } + if (!body || !body.hash) { + logger.log('error', + 'No results found'); + res.status(501).send(); + return; } res.send({ txid: body.hash, From 358662fb84a20b78e0e1f57f030a079b7188a988 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 11:38:40 -0400 Subject: [PATCH 16/44] add tx paging and protections --- server/lib/api/address.js | 7 ++++--- server/lib/api/transaction.js | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/server/lib/api/address.js b/server/lib/api/address.js index c12376a..edcbdcd 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -47,25 +47,26 @@ module.exports = function AddressAPI(router) { logger.log('error', `${err}`); } + const totalReceived = body.reduce((sum, tx) => sum + tx.outputs.reduce((sum, output) => { if (output.address === req.params.addr) { return sum + output.value; } return sum; - }, 0), 0); + }, 0), 0) || 0; const totalSpent = body.reduce((sum, tx) => sum + tx.inputs.reduce((sum, input) => { if (input.coin && input.coin.address === req.params.addr) { return sum + input.coin.value; } return sum; - }, 0), 0); + }, 0), 0) || 0; res.json({ addrStr: req.params.addr, - balance: totalReceived - totalSpent, + balance: (totalReceived - totalSpent) / 1e8, balanceSat: totalReceived - totalSpent, totalReceived: totalReceived / 1e8, totalReceivedSat: totalReceived, diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 397537f..5b7d7fd 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -4,7 +4,7 @@ const logger = require('../logger'); const request = require('request'); const config = require('../../config'); -const MAX_TXS = 20; +const MAX_TXS = 10; const MAX_BLOCKS = 1; // Shoe horned in. Not dry, also in blocks. Make db api later @@ -100,6 +100,10 @@ module.exports = function transactionAPI(router) { // That callback hell router.get('/txs', (req, res) => { + const pageNum = parseInt(req.query.pageNum) || 0; + const rangeStart = pageNum * MAX_TXS; + const rangeEnd = rangeStart + MAX_TXS; + if (req.query.block) { getBlock( {}, @@ -111,6 +115,7 @@ module.exports = function transactionAPI(router) { logger.log('err', err); } if (block[0]) { + const height = block[0].height; request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/block/${req.query.block}`, (err, localRes, body) => { if (err) { @@ -122,9 +127,18 @@ module.exports = function transactionAPI(router) { } catch (e) { logger.log('error', `${err}`); + res.status(501).send(); } + if (!body.txs.length) { + logger.log('error', + `${'No tx results'}`); + res.status(501).send(); + } + const totalPages = Math.ceil(body.txs.length / MAX_TXS); + body.txs = body.txs.slice(rangeStart, rangeEnd); + res.send({ - pagesTotal: 1, + pagesTotal: totalPages, txs: body.txs.map(tx => ({ txid: tx.hash, fees: tx.fee / 1e8, From afd68116e71a27d7049d7e1e10d665eac7aed9b7 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 15:41:25 -0400 Subject: [PATCH 17/44] broadcast tx --- server/lib/api/block.js | 8 +++++--- server/lib/api/index.js | 9 +++++++-- server/lib/api/transaction.js | 21 ++++++++++++++++++--- server/package.json | 1 + 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/server/lib/api/block.js b/server/lib/api/block.js index 2ff49ed..2141347 100644 --- a/server/lib/api/block.js +++ b/server/lib/api/block.js @@ -53,7 +53,7 @@ module.exports = function BlockAPI(router) { poolInfo: {}, }); } else { - res.send(); + res.status(404).send('Not Found'); } }); }); @@ -107,8 +107,10 @@ module.exports = function BlockAPI(router) { }); router.get('/block-index/:height', (req, res) => { + let blockHeight = parseInt(req.params.height) || 1; + getBlock( - { height: req.params.height }, + { height: blockHeight }, { hash: 1 }, MAX_BLOCKS, (err, block) => { @@ -121,7 +123,7 @@ module.exports = function BlockAPI(router) { blockHash: block[0].hash, }); } else { - res.send(); + res.status(404).send('Not Found'); } }); }); diff --git a/server/lib/api/index.js b/server/lib/api/index.js index 0d4610e..e331c37 100644 --- a/server/lib/api/index.js +++ b/server/lib/api/index.js @@ -1,5 +1,6 @@ -const express = require('express'); -const config = require('../../config'); +const express = require('express'); +const config = require('../../config'); +const bodyParser = require('body-parser'); const app = express(); const api = express.Router(); @@ -7,6 +8,10 @@ const cors = require('./cors'); app.use(cors); +app.use(bodyParser.urlencoded({ extended: false })) + +app.use(bodyParser.json()) + // Serve insight ui front end from root dir public folder app.use('/', express.static('./public')); app.use('/blocks', express.static('./public')); diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 5b7d7fd..472337a 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -52,6 +52,7 @@ module.exports = function transactionAPI(router) { } if (block[0]) { const height = block[0].height; + request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/tx/${req.params.txid}`, (err, localRes, body) => { if (err) { logger.log('error', @@ -68,7 +69,7 @@ module.exports = function transactionAPI(router) { if (!body || !body.hash) { logger.log('error', 'No results found'); - res.status(501).send(); + res.status(404).send(); return; } res.send({ @@ -115,7 +116,6 @@ module.exports = function transactionAPI(router) { logger.log('err', err); } if (block[0]) { - const height = block[0].height; request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/block/${req.query.block}`, (err, localRes, body) => { if (err) { @@ -250,6 +250,21 @@ module.exports = function transactionAPI(router) { }); router.post('/tx/send', (req, res) => { - res.send('tx send stub'); + const rawtx = req.body.rawtx || ''; + console.log(rawtx); + request.post({ + url: `http://${config.bcoin_http}:${config.bcoin['http-port']}/broadcast`, + body: {"tx": rawtx }, + json: true, + }, (err, localRes, body) => { + if (err) { + logger.log('error', + `${err}`); + res.status(400).send(err); + return; + } + + res.json(true); + }); }); }; diff --git a/server/package.json b/server/package.json index 3305847..998ff30 100644 --- a/server/package.json +++ b/server/package.json @@ -14,6 +14,7 @@ "license": "MIT", "dependencies": { "bcoin": "^1.0.0-beta.14", + "body-parser": "^1.17.2", "express": "^4.15.3", "mongoose": "^4.11.5", "request": "^2.81.0", From 0c04a55fd09db014ac3eaed921696f1ca6f8060e Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 20:15:01 -0400 Subject: [PATCH 18/44] use app 404 page and clean up console log --- server/lib/api/index.js | 6 +++++- server/lib/api/transaction.js | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/server/lib/api/index.js b/server/lib/api/index.js index e331c37..8af680b 100644 --- a/server/lib/api/index.js +++ b/server/lib/api/index.js @@ -10,17 +10,21 @@ app.use(cors); app.use(bodyParser.urlencoded({ extended: false })) -app.use(bodyParser.json()) +app.use(bodyParser.json()); // Serve insight ui front end from root dir public folder app.use('/', express.static('./public')); +app.use('/:stuff', 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('/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); diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 472337a..a7968b4 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -251,7 +251,6 @@ module.exports = function transactionAPI(router) { router.post('/tx/send', (req, res) => { const rawtx = req.body.rawtx || ''; - console.log(rawtx); request.post({ url: `http://${config.bcoin_http}:${config.bcoin['http-port']}/broadcast`, body: {"tx": rawtx }, From 1777be323c8b2ade576cf1b8c97ce883c9e92861 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 20:29:38 -0400 Subject: [PATCH 19/44] Message verification and bitcore-message added --- server/lib/api/message.js | 30 ++++++++++++++++++++++++++++-- server/package.json | 1 + 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/server/lib/api/message.js b/server/lib/api/message.js index cc71ed6..59b08aa 100644 --- a/server/lib/api/message.js +++ b/server/lib/api/message.js @@ -1,10 +1,36 @@ +const Message = require('bitcore-message'); + +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 (!address || !signature || !message) { + res.json({ + message: 'Missing parameters (expected "address", "signature" and "message")', + code: 1, + }); + return; + } + let valid; + try { + valid = new Message(message).verify(address, signature); + } catch (err) { + res.json({ + message: `Unexpected error: ${err.message}`, + code: 1, + }); + return; + } + res.json({ result: valid }); +} + module.exports = function messageAPI(router) { router.get('/messages/verify', (req, res) => { - res.send('messages verify'); + verifyMessage(req, res); }); router.post('/messages/verify', (req, res) => { - res.send('post messages verify'); + verifyMessage(req, res); }); router.get('/utils/estimatefee', (req, res) => { diff --git a/server/package.json b/server/package.json index 998ff30..7a31453 100644 --- a/server/package.json +++ b/server/package.json @@ -14,6 +14,7 @@ "license": "MIT", "dependencies": { "bcoin": "^1.0.0-beta.14", + "bitcore-message": "^1.0.4", "body-parser": "^1.17.2", "express": "^4.15.3", "mongoose": "^4.11.5", From d278dbdb7ca72a856f010e9b4c18f101b87a25fe Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 21:29:59 -0400 Subject: [PATCH 20/44] Blocks DB API up --- server/lib/api/address.js | 9 ++++--- server/lib/db/blocks.js | 57 +++++++++++++++++++++++++++++++++++++++ server/lib/db/index.js | 7 ++++- 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 server/lib/db/blocks.js diff --git a/server/lib/api/address.js b/server/lib/api/address.js index edcbdcd..d7594f1 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -1,7 +1,10 @@ -const Block = require('../../models/block.js'); -const logger = require('../logger'); +const Block = require('../../models/block.js'); +const logger = require('../logger'); const request = require('request'); -const config = require('../../config'); +const config = require('../../config'); +const db = require('../db'); + +console.log(db); const MAX_BLOCKS = 200; diff --git a/server/lib/db/blocks.js b/server/lib/db/blocks.js new file mode 100644 index 0000000..cdc200c --- /dev/null +++ b/server/lib/db/blocks.js @@ -0,0 +1,57 @@ +const Block = require('../../models/block.js'); +const logger = require('../logger'); +const config = require('../../config'); + +// move to config +const MAX_BLOCKS = 200; +const blockTemplate = new Block(); + +function getBlocks(params, options, limit, cb) { + const defaultOptions = { _id: 0 }; + + Object.assign(defaultOptions, options); + + if (!Number.isInteger(limit)) { + limit = MAX_BLOCKS; + } + + if (limit > MAX_BLOCKS) { + limit = MAX_BLOCKS; + } + + Block.find( + params, + defaultOptions, + (err, blocks) => { + if (err) { + logger.log('error', + `getBlocks: ${err}`); + return cb(err); + } + if (blocks.length > 0) { + return cb(null, blocks); + } + return cb(null, [blockTemplate]); + }) + .sort({ height: -1 }) + .limit(limit); +} + +function getBlock(params, options, limit, cb) { + getBlocks(params, options, limit, (err, blocks) => { + if (err) { + logger.log('error', + `getBlock: ${err}`); + return cb(err); + } + if (blocks.length > 0) { + return cb(null, blocks[0]); + } + return cb(null, blockTemplate); + }); +} + +module.exports = { + getBlock, + getBlocks, +}; diff --git a/server/lib/db/index.js b/server/lib/db/index.js index 43f325f..f808838 100644 --- a/server/lib/db/index.js +++ b/server/lib/db/index.js @@ -1,5 +1,6 @@ const mongoose = require('mongoose'); const logger = require('../logger'); +const Blocks = require('./blocks'); mongoose.connection.on('error', (err) => { logger.log('error', @@ -7,4 +8,8 @@ mongoose.connection.on('error', (err) => { ${err}`); }); -module.exports = mongoose; +module.exports = { + connect: mongoose.connect, + connection: mongoose.connection, + Blocks, +}; From ecee54c10b3285ccc0f45e92156a4d1ac33785cb Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 21:55:48 -0400 Subject: [PATCH 21/44] Address API cleaned up. Unnecessary db calls removed --- server/lib/api/address.js | 172 +++++++++++--------------------------- server/lib/db/index.js | 2 +- 2 files changed, 48 insertions(+), 126 deletions(-) diff --git a/server/lib/api/address.js b/server/lib/api/address.js index d7594f1..a22c5ca 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -1,90 +1,60 @@ -const Block = require('../../models/block.js'); const logger = require('../logger'); const request = require('request'); const config = require('../../config'); -const db = require('../db'); -console.log(db); - -const MAX_BLOCKS = 200; - -// Shoe horned in. Not dry, also in blocks. Make db api later -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); -} +const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; module.exports = function AddressAPI(router) { router.get('/addr/:addr', (req, res) => { - getBlock( - {}, - { height: 1 }, - 1, - (err, block) => { - if (err) { - res.status(501).send(); - logger.log('err', err); + const addr = req.params.addr || ''; + + request(`${API_URL}/tx/address/${addr}`, + (error, bcoinRes, txs) => { + if (error) { + logger.log('error', + `${error}`); } - if (block[0]) { - const height = block[0].height; - request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/tx/address/${req.params.addr}`, (err, localRes, body) => { - if (err) { - logger.log('error', - `${err}`); - } - try { - body = JSON.parse(body); - } catch (e) { - logger.log('error', - `${err}`); - } - - const totalReceived = body.reduce((sum, tx) => sum + tx.outputs.reduce((sum, output) => { - if (output.address === req.params.addr) { - return sum + output.value; - } - return sum; - }, 0), 0) || 0; - - const totalSpent = body.reduce((sum, tx) => sum + tx.inputs.reduce((sum, input) => { - if (input.coin && input.coin.address === req.params.addr) { - return sum + input.coin.value; - } - return sum; - }, 0), 0) || 0; - - - - 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: body.length, - }); - }); + try { + txs = JSON.parse(txs); + } catch (e) { + logger.log('error', + `${e}`); } + + // Sum the matching outputs for every tx + const totalReceived = txs.reduce((sum, tx) => sum + 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((sum, tx) => sum + 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 + 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) => { res.send('1'); }); @@ -114,58 +84,10 @@ module.exports = function AddressAPI(router) { }); router.get('/addrs/:addrs/txs', (req, res) => { - getBlock( - { - $or: - [ - { 'txs.outputs.address': req.params.addr }, - { 'txs.inputs.prevout.hash': req.params.addr }, - ], - }, - { rawBlock: 0 }, - MAX_BLOCKS, - (err, block) => { - if (err) { - res.status(501).send(); - logger.log('err', err); - } - - if (block[0]) { - const b = block[0]; - res.json({ - pagesTotal: 1, - txs: b.txs.map(tx => ({ - txid: tx.hash, - version: tx.version, - locktime: tx.locktime, - vin: tx.inputs.map(input => ({ - coinbase: input.script, - sequence: input.sequence, - n: 0, - addr: input.address, - })), - vout: tx.outputs.map(output => ({ - value: output.value / 1e8, - n: 0, - scriptPubKey: { - hex: '', - asm: '', - addresses: [output.address], - type: output.type, - }, - spentTxid: '', - spentIndex: 0, - spentHeight: 0, - })), - })), - }); - } else { - res.send(); - } - }); + res.send('8'); }); router.post('/addrs/txs', (req, res) => { - res.send('post stub'); + res.send('9'); }); }; diff --git a/server/lib/db/index.js b/server/lib/db/index.js index f808838..72d75ba 100644 --- a/server/lib/db/index.js +++ b/server/lib/db/index.js @@ -11,5 +11,5 @@ mongoose.connection.on('error', (err) => { module.exports = { connect: mongoose.connect, connection: mongoose.connection, - Blocks, + blocks: Blocks, }; From b0f95d93ab8c78305e3f5d799ef8589874755d06 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 23:14:24 -0400 Subject: [PATCH 22/44] All API but tx & socket cleaned up, blocks db api done --- server/lib/api/block.js | 130 ++++++++++++++-------------------- server/lib/api/index.js | 13 +--- server/lib/api/status.js | 112 ++++++++++++++++------------- server/lib/api/transaction.js | 14 ++-- server/lib/db/blocks.js | 14 ++-- server/models/address.js | 6 +- server/models/block.js | 28 ++++---- server/models/input.js | 10 +-- server/models/output.js | 8 +-- server/models/transaction.js | 26 +++---- 10 files changed, 172 insertions(+), 189 deletions(-) diff --git a/server/lib/api/block.js b/server/lib/api/block.js index 2141347..fb64178 100644 --- a/server/lib/api/block.js +++ b/server/lib/api/block.js @@ -1,66 +1,44 @@ -const Block = require('../../models/block.js'); const logger = require('../logger'); - -const MAX_BLOCKS = 100; - -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); -} +const db = require('../db'); module.exports = function BlockAPI(router) { router.get('/block/:blockHash', (req, res) => { - getBlock( + db.blocks.getBlock( { hash: req.params.blockHash }, { rawBlock: 0 }, - MAX_BLOCKS, + 1, (err, block) => { if (err) { - res.status(501).send(); logger.log('err', err); + return res.status(404).send(); } - if (block[0]) { - const b = block[0]; - res.json({ - hash: b.hash, - size: b.size, - height: b.height, - version: b.version, - merkleroot: b.merkleRoot, - tx: b.txs, - time: b.ts, - nonce: b.nonce, - bits: b.bits.toString(16), - difficulty: 1, - chainwork: b.chainwork.toString(16), - confirmations: 0, - previousblockhash: b.prevBlock, - nextblockhash: 0, - reward: b.reward / 1e8, - isMainChain: true, - poolInfo: {}, - }); - } else { - res.status(404).send('Not Found'); - } + + return res.json({ + hash: block.hash, + size: block.size, + height: block.height, + version: block.version, + merkleroot: block.merkleRoot, + tx: block.txs, + time: block.ts, + nonce: block.nonce, + bits: block.bits.toString(16), + difficulty: 1, + chainwork: block.chainwork.toString(16), + confirmations: 0, + previousblockhash: block.prevBlock, + nextblockhash: 0, + reward: block.reward / 1e8, + isMainChain: true, + poolInfo: {}, + }); }); }); router.get('/blocks', (req, res) => { - const limit = parseInt(req.query.limit) || MAX_BLOCKS; - getBlock( + const limit = parseInt(req.query.limit) || 100; + + db.blocks.getBlocks( {}, { height: 1, size: 1, @@ -72,59 +50,59 @@ module.exports = function BlockAPI(router) { limit, (err, blocks) => { if (err) { - res.status(501).send(); - logger.log('err', err); - return; + logger.log('err', + `/blocks: ${err}`); + return res.status(404).send(); } - res.json({ + return res.json({ blocks: blocks.map(block => ({ - hash: block.hash, - height: block.height, - size: block.size, - time: block.ts, + hash: block.hash, + height: block.height, + size: block.size, + time: block.ts, txlength: block.txs.length, poolInfo: {}, })), - length: blocks.length, + length: blocks.length, pagination: {}, }); }); }); router.get('/rawblock/:blockHash', (req, res) => { - getBlock( - { hash: req.params.blockHash }, + const blockHash = req.params.blockHash || ''; + + db.blocks.getBlock( + { hash: blockHash }, { rawBlock: 1 }, - MAX_BLOCKS, + 1, (err, block) => { if (err) { - res.status(501).send(); - logger.log('err', err); + logger.log('err', + `/rawblock/:blockHash: ${err}`); + return res.status(404).send(); } - res.json(block[0]); + return res.json(block); }); }); router.get('/block-index/:height', (req, res) => { - let blockHeight = parseInt(req.params.height) || 1; + const blockHeight = parseInt(req.params.height) || 1; - getBlock( + db.blocks.getBlock( { height: blockHeight }, { hash: 1 }, - MAX_BLOCKS, + 1, (err, block) => { if (err) { - res.status(501).send(); - logger.log('err', err); - } - if (block[0]) { - res.json({ - blockHash: block[0].hash, - }); - } else { - res.status(404).send('Not Found'); + logger.log('err', + `/block-index/:height: ${err}`); + return res.status(404).send(); } + return res.json({ + blockHash: block.hash, + }); }); }); }; diff --git a/server/lib/api/index.js b/server/lib/api/index.js index 8af680b..dfe702c 100644 --- a/server/lib/api/index.js +++ b/server/lib/api/index.js @@ -13,18 +13,7 @@ app.use(bodyParser.urlencoded({ extended: false })) app.use(bodyParser.json()); // Serve insight ui front end from root dir public folder -app.use('/', express.static('./public')); -app.use('/:stuff', 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('/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.use(express.static('./public')); app.set('json spaces', config.api.json_spaces); diff --git a/server/lib/api/status.js b/server/lib/api/status.js index 758e55a..a8f82b8 100644 --- a/server/lib/api/status.js +++ b/server/lib/api/status.js @@ -1,87 +1,99 @@ const request = require('request'); -const Block = require('../../models/block'); const pkg = require('../../package.json'); const config = require('../../config'); const netCfg = require('bcoin/lib/net/common'); const logger = require('../logger'); +const db = require('../db'); + +const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}/`; -// Here comes the ugly. Moo who haha function getStatus(cb) { - request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/`, (err, localRes, body) => { + request(`${API_URL}`, (err, localRes, status) => { if (err) { logger.log('error', - `${err}`); + `getStatus ${err}`); + return cb(err); } try { - body = JSON.parse(body); + status = JSON.parse(status); } catch (e) { logger.log('error', - `${err}`); - cb(e); + `getStatus JSON.parse: ${e}`); + return cb(e); } - cb(null, body); + return cb(null, status); }); } - +// UI assigns Multiple Responsibilities depending on params module.exports = function statusAPI(router) { router.get('/status', (req, res) => { if (req.query.q === 'getLastBlockHash') { - Block.findOne({}, { 'hash': 1 }, { sort: { 'height': -1 } }, (err, block) => { - if (err) { - logger.log('error', - `${err}`); - res.status(501).send(err); - } else { - res.send({ + db.blocks.getBlock( + {}, + { hash: 1 }, + 1, + (err, block) => { + if (err) { + logger.log('error', + `${err}`); + return res.status(404).send(err); + } + return res.send({ syncTipHash: block.hash, lastblockhash: block.hash, }); - } - }); + }); } else { getStatus((err, status) => { if (err) { - res.status(501).send(err); - } else if (status) { - 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, - }, - }); - } else { - res.send(); + 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(); + } + 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, + }, + }); }); } - }); router.get('/sync', (req, res) => { getStatus((err, status) => { if (err) { - res.status(501).send(err); - } else if (status) { - res.json({ - status: 'syncing', - blockChainHeight: status.chain.height, - syncPercentage: Math.round(status.chain.progress * 100), - height: status.chain.height, - error: null, - type: 'bcoin node', - }); - } else { - res.send(); + logger.log('err', + `/sync: ${err}`); + return res.status(404).send(err); } + if (!status) { + logger.log('err', + '/sync: no status'); + return res.status(404).send(); + } + 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', + }); }); }); diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index a7968b4..3a15609 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -47,7 +47,7 @@ module.exports = function transactionAPI(router) { 1, (err, block) => { if (err) { - res.status(501).send(); + res.status(404).send(); logger.log('err', err); } if (block[0]) { @@ -63,7 +63,7 @@ module.exports = function transactionAPI(router) { } catch (e) { logger.log('error', `${err}`); - res.status(501).send(); + res.status(404).send(); return; } if (!body || !body.hash) { @@ -112,7 +112,7 @@ module.exports = function transactionAPI(router) { 1, (err, block) => { if (err) { - res.status(501).send(); + res.status(404).send(); logger.log('err', err); } if (block[0]) { @@ -127,12 +127,12 @@ module.exports = function transactionAPI(router) { } catch (e) { logger.log('error', `${err}`); - res.status(501).send(); + res.status(404).send(); } if (!body.txs.length) { logger.log('error', `${'No tx results'}`); - res.status(501).send(); + res.status(404).send(); } const totalPages = Math.ceil(body.txs.length / MAX_TXS); body.txs = body.txs.slice(rangeStart, rangeEnd); @@ -167,7 +167,7 @@ module.exports = function transactionAPI(router) { 1, (err, block) => { if (err) { - res.status(501).send(); + res.status(404).send(); logger.log('err', err); } if (block[0]) { @@ -212,7 +212,7 @@ module.exports = function transactionAPI(router) { {}, (err, txs) => { if (err) { - res.status(501).send(); + res.status(404).send(); } res.json({ pagesTotal: 1, diff --git a/server/lib/db/blocks.js b/server/lib/db/blocks.js index cdc200c..be2ad62 100644 --- a/server/lib/db/blocks.js +++ b/server/lib/db/blocks.js @@ -3,7 +3,7 @@ const logger = require('../logger'); const config = require('../../config'); // move to config -const MAX_BLOCKS = 200; +const MAX_BLOCKS = 50; const blockTemplate = new Block(); function getBlocks(params, options, limit, cb) { @@ -12,13 +12,17 @@ function getBlocks(params, options, limit, cb) { Object.assign(defaultOptions, options); if (!Number.isInteger(limit)) { - limit = MAX_BLOCKS; + limit = 1; } if (limit > MAX_BLOCKS) { limit = MAX_BLOCKS; } + if (limit < 1) { + limit = 1; + } + Block.find( params, defaultOptions, @@ -28,10 +32,10 @@ function getBlocks(params, options, limit, cb) { `getBlocks: ${err}`); return cb(err); } - if (blocks.length > 0) { - return cb(null, blocks); + if (!blocks.length > 0) { + return cb({err: 'Block not found'}); } - return cb(null, [blockTemplate]); + return cb(null, blocks); }) .sort({ height: -1 }) .limit(limit); diff --git a/server/models/address.js b/server/models/address.js index 728b5f4..20fd32b 100644 --- a/server/models/address.js +++ b/server/models/address.js @@ -5,9 +5,9 @@ const Output = require('./output'); const Schema = mongoose.Schema; const AddressSchema = new Schema({ - address: String, - inputs: [Input.schema], - outputs: [Output.schema], + address: { type: String, default: '' }, + inputs: [Input.schema], + outputs: [Output.schema], }); const Address = mongoose.model('Address', AddressSchema); diff --git a/server/models/block.js b/server/models/block.js index 515fe70..38ba3aa 100644 --- a/server/models/block.js +++ b/server/models/block.js @@ -4,21 +4,21 @@ const Transaction = require('./transaction'); const Schema = mongoose.Schema; const BlockSchema = new Schema({ - hash: String, - height: Number, - size: Number, - version: Number, - prevBlock: String, - merkleRoot: String, - ts: Number, - bits: Number, - nonce: Number, + hash: { type: String, default: '' }, + height: { type: Number, default: 0 }, + size: { type: Number, default: 0 }, + version: { type: Number, default: 0 }, + prevBlock: { type: String, default: '' }, + merkleRoot: { type: String, default: '' }, + ts: { type: Number, default: 0 }, + bits: { type: Number, default: 0 }, + nonce: { type: Number, default: 0 }, txs: [Transaction.schema], - chainwork: Number, - reward: Number, - network: String, - poolInfo: Object, - rawBlock: String, + chainwork: { type: Number, default: 0 }, + reward: { type: Number, default: 0 }, + network: { type: String, default: '' }, + poolInfo: { type: Object, default: {} }, + rawBlock: { type: String, default: '' }, }, { toJSON: { virtuals: true, diff --git a/server/models/input.js b/server/models/input.js index 9f5f0a9..9f2c067 100644 --- a/server/models/input.js +++ b/server/models/input.js @@ -3,11 +3,11 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; const InputSchema = new Schema({ - prevout: Object, - script: String, - witness: String, - sequence: Number, - address: String, + prevout: { type: Object, default: {} }, + script: { type: String, default: '' }, + witness: { type: String, default: '' }, + sequence: { type: Number, default: 0 }, + address: { type: String, default: '' }, }); const Input = mongoose.model('Input', InputSchema); diff --git a/server/models/output.js b/server/models/output.js index 709445b..65f2194 100644 --- a/server/models/output.js +++ b/server/models/output.js @@ -3,10 +3,10 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; const OutputSchema = new Schema({ - address: String, - script: String, - value: Number, - type: String, + address: { type: String, default: '' }, + script: { type: String, default: '' }, + value: { type: Number, default: 0 }, + type: { type: String, default: '' }, }); const Output = mongoose.model('Output', OutputSchema); diff --git a/server/models/transaction.js b/server/models/transaction.js index 73c4f4d..0c5974b 100644 --- a/server/models/transaction.js +++ b/server/models/transaction.js @@ -5,21 +5,21 @@ const Output = require('./output'); const Schema = mongoose.Schema; const TransactionSchema = new Schema({ - hash: String, - witnessHash: String, - fee: Number, - rate: Number, - ps: Number, - height: Number, - block: String, - index: Number, - version: Number, - flag: Number, - lockTime: Number, + hash: { type: String, default: '' }, + witnessHash: { type: String, default: '' }, + fee: { type: Number, default: 0 }, + rate: { type: Number, default: 0 }, + ps: { type: Number, default: 0 }, + height: { type: Number, default: 0 }, + block: { type: String, default: '' }, + index: { type: Number, default: 0 }, + version: { type: Number, default: 0 }, + flag: { type: Number, default: 0 }, + lockTime: { type: Number, default: 0 }, inputs: [Input.schema], outputs: [Output.schema], - size: Number, - network: String, + size: { type: Number, default: 0 }, + network: { type: String, default: '' }, }); const Transaction = mongoose.model('Transaction', TransactionSchema); From 1d029fcf7f5eb30bfe3ec1aabc57805adde6364b Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Tue, 15 Aug 2017 23:58:09 -0400 Subject: [PATCH 23/44] getBlocks completely in db api --- server/lib/api/address.js | 6 +- server/lib/api/index.js | 11 ++ server/lib/api/status.js | 2 +- server/lib/api/transaction.js | 273 ++++++++++++++++------------------ server/lib/db/blocks.js | 8 +- 5 files changed, 147 insertions(+), 153 deletions(-) diff --git a/server/lib/api/address.js b/server/lib/api/address.js index a22c5ca..47cc1dd 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -7,18 +7,20 @@ const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; module.exports = function AddressAPI(router) { router.get('/addr/:addr', (req, res) => { const addr = req.params.addr || ''; - + // Get Bcoin data request(`${API_URL}/tx/address/${addr}`, (error, bcoinRes, txs) => { if (error) { logger.log('error', `${error}`); + return res.status(404).send({}); } try { txs = JSON.parse(txs); } catch (e) { logger.log('error', `${e}`); + return res.status(404).send({}); } // Sum the matching outputs for every tx @@ -38,7 +40,7 @@ module.exports = function AddressAPI(router) { }, 0), 0) || 0; // Match Insight API - res.json({ + return res.json({ addrStr: req.params.addr, balance: (totalReceived - totalSpent) / 1e8, balanceSat: totalReceived - totalSpent, diff --git a/server/lib/api/index.js b/server/lib/api/index.js index dfe702c..c871e61 100644 --- a/server/lib/api/index.js +++ b/server/lib/api/index.js @@ -14,6 +14,17 @@ app.use(bodyParser.json()); // Serve insight ui front end from root dir public folder app.use(express.static('./public')); +app.use('/:stuff', 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('/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); diff --git a/server/lib/api/status.js b/server/lib/api/status.js index a8f82b8..f9bdb60 100644 --- a/server/lib/api/status.js +++ b/server/lib/api/status.js @@ -46,7 +46,7 @@ module.exports = function statusAPI(router) { } else { getStatus((err, status) => { if (err) { - logger.log('err' + logger.log('err', `/status getStatus: ${err}`); return res.status(404).send(err); } diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 3a15609..e074773 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -1,30 +1,11 @@ -const Block = require('../../models/block.js'); const Transaction = require('../../models/transaction'); const logger = require('../logger'); const request = require('request'); const config = require('../../config'); +const db = require('../db'); const MAX_TXS = 10; -const MAX_BLOCKS = 1; - -// Shoe horned in. Not dry, also in blocks. Make db api later -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); -} - +const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; function getTransactions(params, options, cb) { const defaultOptions = { _id: 0 }; @@ -41,61 +22,60 @@ function getTransactions(params, options, cb) { module.exports = function transactionAPI(router) { router.get('/tx/:txid', (req, res) => { - getBlock( + db.blocks.getBlock( {}, { height: 1 }, 1, (err, block) => { if (err) { - res.status(404).send(); logger.log('err', err); + return res.status(404).send(); } - if (block[0]) { - const height = block[0].height; - request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/tx/${req.params.txid}`, (err, localRes, body) => { - if (err) { - logger.log('error', - `${err}`); - } - try { - body = JSON.parse(body); - } catch (e) { - logger.log('error', - `${err}`); - res.status(404).send(); - return; - } - if (!body || !body.hash) { - logger.log('error', - 'No results found'); - res.status(404).send(); - return; - } - res.send({ - txid: body.hash, - version: body.version, - time: body.ps, - blocktime: body.ps, - locktime: body.locktime, - blockhash: body.block, - fees: body.fee / 1e8, - confirmations: height - body.height + 1, - valueOut: body.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, - vin: body.inputs.map(input => ({ - addr: input.coin ? input.coin.address : '', - value: input.coin ? input.coin.value / 1e8 : 0, - })), - vout: body.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - isCoinbase: body.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', - }); + const height = block.height; + + request(`${API_URL}/tx/${req.params.txid}`, (error, localRes, body) => { + if (error) { + logger.log('error', + `${error}`); + } + try { + body = JSON.parse(body); + } catch (e) { + logger.log('error', + `${e}`); + res.status(404).send(); + return; + } + if (!body || !body.hash) { + logger.log('error', + 'No results found'); + res.status(404).send(); + return; + } + res.send({ + txid: body.hash, + version: body.version, + time: body.ps, + blocktime: body.ps, + locktime: body.locktime, + blockhash: body.block, + fees: body.fee / 1e8, + confirmations: height - body.height + 1, + valueOut: body.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, + vin: body.inputs.map(input => ({ + addr: input.coin ? input.coin.address : '', + value: input.coin ? input.coin.value / 1e8 : 0, + })), + vout: body.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + isCoinbase: body.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', }); - } + }); }); }); @@ -106,105 +86,106 @@ module.exports = function transactionAPI(router) { const rangeEnd = rangeStart + MAX_TXS; if (req.query.block) { - getBlock( + db.blocks.getBlock( {}, { height: 1 }, 1, (err, block) => { if (err) { - res.status(404).send(); logger.log('err', err); + return res.status(404).send(); } - if (block[0]) { - const height = block[0].height; - request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/block/${req.query.block}`, (err, localRes, body) => { - if (err) { - logger.log('error', - `${err}`); - } - try { - body = JSON.parse(body); - } catch (e) { - logger.log('error', - `${err}`); - res.status(404).send(); - } - if (!body.txs.length) { - logger.log('error', - `${'No tx results'}`); - res.status(404).send(); - } - const totalPages = Math.ceil(body.txs.length / MAX_TXS); - body.txs = body.txs.slice(rangeStart, rangeEnd); + const height = block.height; + request(`${API_URL}/block/${req.query.block}`, (error, localRes, block) => { + if (error) { + logger.log('error', + `${error}`); + } + try { + block = JSON.parse(block); + } catch (e) { + logger.log('error', + `${e}`); + return res.status(404).send(); + } + if (!block.txs.length) { + logger.log('error', + `${'No tx results'}`); + res.status(404).send(); + } + const totalPages = Math.ceil(block.txs.length / MAX_TXS); + block.txs = block.txs.slice(rangeStart, rangeEnd); - res.send({ - pagesTotal: totalPages, - txs: body.txs.map(tx => ({ - txid: tx.hash, - fees: tx.fee / 1e8, - confirmations: height - body.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, - })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - output: tx.outputs, + 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, })), - }); + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + output: tx.outputs, + })), }); - } + }); }); } else if (req.query.address) { - getBlock( + db.blocks.getBlock( {}, { height: 1 }, 1, (err, block) => { if (err) { - res.status(404).send(); logger.log('err', err); + return res.status(404).send(); } - if (block[0]) { - const height = block[0].height; - request(`http://${config.bcoin_http}:${config.bcoin['http-port']}/tx/address/${req.query.address}`, (err, localRes, body) => { - if (err) { - logger.log('error', - `${err}`); - } - try { - body = JSON.parse(body); - } catch (e) { - logger.log('error', - `${err}`); - } - res.send({ - pagesTotal: 1, - txs: body.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, - })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - output: tx.outputs, + + const height = block.height; + const addr = req.query.address || ''; + + request(`${API_URL}/tx/address/${req.query.address}`, (error, localRes, txs) => { + if (error) { + logger.log('error', + `${error}`); + return res.status(404).send(); + } + try { + txs = JSON.parse(txs); + } catch (e) { + logger.log('error', + `${e}`); + return res.status(404).send(); + } + 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, })), - }); + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + output: tx.outputs, + })), }); - } + }); }); } else { getTransactions( @@ -253,7 +234,7 @@ module.exports = function transactionAPI(router) { const rawtx = req.body.rawtx || ''; request.post({ url: `http://${config.bcoin_http}:${config.bcoin['http-port']}/broadcast`, - body: {"tx": rawtx }, + body: { tx: rawtx }, json: true, }, (err, localRes, body) => { if (err) { diff --git a/server/lib/db/blocks.js b/server/lib/db/blocks.js index be2ad62..fcaa63b 100644 --- a/server/lib/db/blocks.js +++ b/server/lib/db/blocks.js @@ -45,13 +45,13 @@ function getBlock(params, options, limit, cb) { getBlocks(params, options, limit, (err, blocks) => { if (err) { logger.log('error', - `getBlock: ${err}`); + `getBlock: ${err.err}`); return cb(err); } - if (blocks.length > 0) { - return cb(null, blocks[0]); + if (!blocks.length > 0) { + return cb(null, blockTemplate); } - return cb(null, blockTemplate); + return cb(null, blocks[0]); }); } From 2eac46fdb593aff0372832586d029a246cc73f37 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 00:17:45 -0400 Subject: [PATCH 24/44] transactions added to db api --- server/lib/api/transaction.js | 32 ++++++------------- server/lib/db/blocks.js | 7 ++-- server/lib/db/index.js | 2 ++ server/lib/db/transactions.js | 60 +++++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 26 deletions(-) create mode 100644 server/lib/db/transactions.js diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index e074773..fef4efc 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -1,24 +1,10 @@ -const Transaction = require('../../models/transaction'); const logger = require('../logger'); const request = require('request'); const config = require('../../config'); const db = require('../db'); -const MAX_TXS = 10; const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; - -function getTransactions(params, options, cb) { - const defaultOptions = { _id: 0 }; - - Object.assign(defaultOptions, options); - - Transaction.find( - params, - defaultOptions, - cb) - .sort({ height: 1 }) - .limit(MAX_TXS); -} +const MAX_TXS = 50; module.exports = function transactionAPI(router) { router.get('/tx/:txid', (req, res) => { @@ -38,22 +24,21 @@ module.exports = function transactionAPI(router) { if (error) { logger.log('error', `${error}`); + return res.status(404).send(); } try { body = JSON.parse(body); } catch (e) { logger.log('error', `${e}`); - res.status(404).send(); - return; + return res.status(404).send(); } if (!body || !body.hash) { logger.log('error', 'No results found'); - res.status(404).send(); - return; + return res.status(404).send(); } - res.send({ + return res.send({ txid: body.hash, version: body.version, time: body.ps, @@ -188,14 +173,17 @@ module.exports = function transactionAPI(router) { }); }); } else { - getTransactions( + db.txs.getTransactions( {}, {}, + 50, (err, txs) => { if (err) { + logger.log('err', + `getTransactions: ${err}`); res.status(404).send(); } - res.json({ + return res.json({ pagesTotal: 1, txs: txs.map(tx => ({ txid: tx.hash, diff --git a/server/lib/db/blocks.js b/server/lib/db/blocks.js index fcaa63b..a417194 100644 --- a/server/lib/db/blocks.js +++ b/server/lib/db/blocks.js @@ -3,8 +3,7 @@ const logger = require('../logger'); const config = require('../../config'); // move to config -const MAX_BLOCKS = 50; -const blockTemplate = new Block(); +const MAX_BLOCKS = 72; // ~ 12 hours function getBlocks(params, options, limit, cb) { const defaultOptions = { _id: 0 }; @@ -33,7 +32,7 @@ function getBlocks(params, options, limit, cb) { return cb(err); } if (!blocks.length > 0) { - return cb({err: 'Block not found'}); + return cb({ err: 'Block not found' }); } return cb(null, blocks); }) @@ -49,7 +48,7 @@ function getBlock(params, options, limit, cb) { return cb(err); } if (!blocks.length > 0) { - return cb(null, blockTemplate); + return cb({ err: 'Block not found' }); } return cb(null, blocks[0]); }); diff --git a/server/lib/db/index.js b/server/lib/db/index.js index 72d75ba..32e5be1 100644 --- a/server/lib/db/index.js +++ b/server/lib/db/index.js @@ -1,6 +1,7 @@ const mongoose = require('mongoose'); const logger = require('../logger'); const Blocks = require('./blocks'); +const Txs = require('./transactions'); mongoose.connection.on('error', (err) => { logger.log('error', @@ -12,4 +13,5 @@ module.exports = { connect: mongoose.connect, connection: mongoose.connection, blocks: Blocks, + txs: Txs, }; diff --git a/server/lib/db/transactions.js b/server/lib/db/transactions.js new file mode 100644 index 0000000..2d160ab --- /dev/null +++ b/server/lib/db/transactions.js @@ -0,0 +1,60 @@ +const Transactions = require('../../models/transaction.js'); +const logger = require('../logger'); +const config = require('../../config'); + +// move to config +const MAX_TXS = 50; + +function getTransactions(params, options, limit, cb) { + const defaultOptions = { _id: 0 }; + + Object.assign(defaultOptions, options); + + if (!Number.isInteger(limit)) { + limit = 1; + } + + if (limit > MAX_TXS) { + limit = MAX_TXS; + } + + if (limit < 1) { + limit = 1; + } + + 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', + `getBlock: ${err.err}`); + return cb(err); + } + if (!tx.length > 0) { + return cb({ err: 'Tx not found' }); + } + return cb(null, tx[0]); + }); +} + +module.exports = { + getTransaction, + getTransactions, +}; From e3b8413eee82bf82dd2bcfce403839046e8e0259 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 00:52:11 -0400 Subject: [PATCH 25/44] Moved db configs to config file. Dropped time from log files. Simple commenting --- server/config/index.js | 3 +++ server/lib/api/block.js | 11 ++++++----- server/lib/api/currency.js | 11 ++++++++--- server/lib/api/transaction.js | 2 +- server/lib/db/blocks.js | 12 ++++++------ server/lib/db/transactions.js | 12 ++++++------ server/lib/logger/index.js | 2 +- server/lib/node/index.js | 2 ++ server/public/src/js/services/socket.js | 1 - 9 files changed, 33 insertions(+), 23 deletions(-) diff --git a/server/config/index.js b/server/config/index.js index 714f954..2dd1526 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -26,6 +26,9 @@ const config = { json_spaces: 2, currency_refresh: 60, ticker_url: 'https://www.bitstamp.net/api/ticker/', + ticker_prop: 'bitstamp', + max_blocks: 72, + max_txs: 10, }, }; diff --git a/server/lib/api/block.js b/server/lib/api/block.js index fb64178..9c3af3c 100644 --- a/server/lib/api/block.js +++ b/server/lib/api/block.js @@ -3,6 +3,7 @@ const db = require('../db'); module.exports = function BlockAPI(router) { router.get('/block/:blockHash', (req, res) => { + // Pass Mongo params, fields and limit to db api. db.blocks.getBlock( { hash: req.params.blockHash }, { rawBlock: 0 }, @@ -12,7 +13,7 @@ module.exports = function BlockAPI(router) { logger.log('err', err); return res.status(404).send(); } - + // Format the request for insight ui return res.json({ hash: block.hash, size: block.size, @@ -37,7 +38,7 @@ module.exports = function BlockAPI(router) { router.get('/blocks', (req, res) => { const limit = parseInt(req.query.limit) || 100; - + // Pass Mongo params, fields and limit to db api. db.blocks.getBlocks( {}, { height: 1, @@ -54,7 +55,7 @@ module.exports = function BlockAPI(router) { `/blocks: ${err}`); return res.status(404).send(); } - + // Format the request for insight ui return res.json({ blocks: blocks.map(block => ({ hash: block.hash, @@ -72,7 +73,7 @@ module.exports = function BlockAPI(router) { router.get('/rawblock/:blockHash', (req, res) => { const blockHash = req.params.blockHash || ''; - + // Pass Mongo params, fields and limit to db api. db.blocks.getBlock( { hash: blockHash }, { rawBlock: 1 }, @@ -89,7 +90,7 @@ module.exports = function BlockAPI(router) { router.get('/block-index/:height', (req, res) => { const blockHeight = parseInt(req.params.height) || 1; - + // Pass Mongo params, fields and limit to db api. db.blocks.getBlock( { height: blockHeight }, { hash: 1 }, diff --git a/server/lib/api/currency.js b/server/lib/api/currency.js index 1c6279d..0e55496 100644 --- a/server/lib/api/currency.js +++ b/server/lib/api/currency.js @@ -2,6 +2,9 @@ const config = require('../../config'); const logger = require('../logger'); const request = require('request'); +// Retrieve the configured endpoint's ticker rate at a +// set interval + const refreshInterval = config.api.currency_refresh >= 1 ? config.api.currency_refresh * 1000 : 60 * 1000; @@ -32,11 +35,13 @@ function getRate() { } module.exports = function currencyAPI(app) { + // Return the ticker price app.get('/currency', (req, res) => { + const data = {} + data[config.api.ticker_prop] = lastRate; + res.json({ - data: { - bitstamp: lastRate, - }, + data, }); }); }; diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index fef4efc..80bcac3 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -4,7 +4,7 @@ const config = require('../../config'); const db = require('../db'); const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; -const MAX_TXS = 50; +const MAX_TXS = config.api.max_txs; module.exports = function transactionAPI(router) { router.get('/tx/:txid', (req, res) => { diff --git a/server/lib/db/blocks.js b/server/lib/db/blocks.js index a417194..e3da8fe 100644 --- a/server/lib/db/blocks.js +++ b/server/lib/db/blocks.js @@ -2,14 +2,14 @@ const Block = require('../../models/block.js'); const logger = require('../logger'); const config = require('../../config'); -// move to config -const MAX_BLOCKS = 72; // ~ 12 hours +const MAX_BLOCKS = config.api.max_blocks; // ~ 12 hours 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; } @@ -21,7 +21,7 @@ function getBlocks(params, options, limit, cb) { if (limit < 1) { limit = 1; } - + // Query mongo Block.find( params, defaultOptions, @@ -39,7 +39,7 @@ function getBlocks(params, options, limit, cb) { .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) { diff --git a/server/lib/db/transactions.js b/server/lib/db/transactions.js index 2d160ab..455727d 100644 --- a/server/lib/db/transactions.js +++ b/server/lib/db/transactions.js @@ -2,14 +2,14 @@ const Transactions = require('../../models/transaction.js'); const logger = require('../logger'); const config = require('../../config'); -// move to config -const MAX_TXS = 50; +const MAX_TXS = config.api.max_txs; 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; } @@ -21,7 +21,7 @@ function getTransactions(params, options, limit, cb) { if (limit < 1) { limit = 1; } - + // Query mongo Transactions.find( params, defaultOptions, @@ -44,7 +44,7 @@ function getTransaction(params, options, limit, cb) { getTransactions(params, options, limit, (err, tx) => { if (err) { logger.log('error', - `getBlock: ${err.err}`); + `getTransaction: ${err.err}`); return cb(err); } if (!tx.length > 0) { diff --git a/server/lib/logger/index.js b/server/lib/logger/index.js index 7db8f9f..2674161 100644 --- a/server/lib/logger/index.js +++ b/server/lib/logger/index.js @@ -1,7 +1,7 @@ const winston = require('winston'); const config = require('../../config'); -const logfile = new Date().toISOString(); +const logfile = new Date().toISOString().split('T')[0]; const logger = new (winston.Logger)({ transports: [ diff --git a/server/lib/node/index.js b/server/lib/node/index.js index 533262d..2c9cc88 100644 --- a/server/lib/node/index.js +++ b/server/lib/node/index.js @@ -6,6 +6,8 @@ const addrParser = require('../parser').Address; const config = require('../../config'); const io = require('../api').io; +// Reverse how sockets are working + const node = new FullNode(config.bcoin); // Hacky move this to config diff --git a/server/public/src/js/services/socket.js b/server/public/src/js/services/socket.js index 1c1ff8f..b4c2987 100644 --- a/server/public/src/js/services/socket.js +++ b/server/public/src/js/services/socket.js @@ -55,7 +55,6 @@ ScopedSocket.prototype.emit = function(event, data, callback) { angular.module('insight.socket').factory('getSocket', function($rootScope) { - console.log('init my socket'); var socket = io.connect('http://localhost', { 'reconnect': true, 'reconnection delay': 500, From 3b8b900405112afef71ef94d453159d06ff5dc6a Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 01:08:05 -0400 Subject: [PATCH 26/44] More cleanup, naming fixes and comments --- server/lib/api/message.js | 1 + server/lib/api/socket.js | 1 + server/lib/api/status.js | 16 ++++++------ server/lib/api/transaction.js | 47 +++++++++++++++++++---------------- server/lib/db/blocks.js | 12 +++++++++ 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/server/lib/api/message.js b/server/lib/api/message.js index 59b08aa..42ba63e 100644 --- a/server/lib/api/message.js +++ b/server/lib/api/message.js @@ -1,5 +1,6 @@ const Message = require('bitcore-message'); +// Copied from previous source function verifyMessage(req, res) { const address = req.body.address || req.query.address; const signature = req.body.signature || req.query.signature; diff --git a/server/lib/api/socket.js b/server/lib/api/socket.js index a0e02ea..ad8577c 100644 --- a/server/lib/api/socket.js +++ b/server/lib/api/socket.js @@ -1,3 +1,4 @@ +// Change to have services push blocks/txs module.exports = function addressrouter(io) { io.on('connection', (socket) => { socket.on('subscribe', (data) => { diff --git a/server/lib/api/status.js b/server/lib/api/status.js index f9bdb60..f273e9d 100644 --- a/server/lib/api/status.js +++ b/server/lib/api/status.js @@ -7,6 +7,7 @@ const db = require('../db'); const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}/`; +// Retrieve Bcoin status function getStatus(cb) { request(`${API_URL}`, (err, localRes, status) => { if (err) { @@ -26,6 +27,7 @@ function getStatus(cb) { } // UI assigns Multiple Responsibilities depending on params module.exports = function statusAPI(router) { + // Get last block hash or node status router.get('/status', (req, res) => { if (req.query.q === 'getLastBlockHash') { db.blocks.getBlock( @@ -73,7 +75,7 @@ module.exports = function statusAPI(router) { }); } }); - + // Get Bcoin sync status router.get('/sync', (req, res) => { getStatus((err, status) => { if (err) { @@ -87,16 +89,16 @@ module.exports = function statusAPI(router) { return res.status(404).send(); } res.json({ - status: status.chain.progress === 100 ? 'synced' : 'syncing', + 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', + 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) => { res.json({ connected: true, diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 80bcac3..007c313 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -7,7 +7,9 @@ const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; const MAX_TXS = config.api.max_txs; module.exports = function transactionAPI(router) { + // Txs by txid router.get('/tx/:txid', (req, res) => { + // Get max block height for calculating confirmations db.blocks.getBlock( {}, { height: 1 }, @@ -19,57 +21,57 @@ module.exports = function transactionAPI(router) { } const height = block.height; - - request(`${API_URL}/tx/${req.params.txid}`, (error, localRes, body) => { + // Bcoin transaction data + request(`${API_URL}/tx/${req.params.txid}`, (error, localRes, tx) => { if (error) { logger.log('error', `${error}`); return res.status(404).send(); } try { - body = JSON.parse(body); + tx = JSON.parse(tx); } catch (e) { logger.log('error', `${e}`); return res.status(404).send(); } - if (!body || !body.hash) { + if (!tx || !tx.hash) { logger.log('error', 'No results found'); return res.status(404).send(); } return res.send({ - txid: body.hash, - version: body.version, - time: body.ps, - blocktime: body.ps, - locktime: body.locktime, - blockhash: body.block, - fees: body.fee / 1e8, - confirmations: height - body.height + 1, - valueOut: body.outputs.reduce((sum, output) => sum + output.value, 0) / 1e8, - vin: body.inputs.map(input => ({ + 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, })), - vout: body.outputs.map(output => ({ + vout: tx.outputs.map(output => ({ scriptPubKey: { addresses: [output.address], }, value: output.value / 1e8, })), - isCoinbase: body.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', + isCoinbase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', }); }); }); }); - // That callback hell + // /txs is overloaded. Next ver separate concerns router.get('/txs', (req, res) => { const pageNum = parseInt(req.query.pageNum) || 0; const rangeStart = pageNum * MAX_TXS; const rangeEnd = rangeStart + MAX_TXS; - + // get txs for blockhash if (req.query.block) { db.blocks.getBlock( {}, @@ -81,6 +83,7 @@ module.exports = function transactionAPI(router) { return res.status(404).send(); } const height = block.height; + // Get Bcoin data request(`${API_URL}/block/${req.query.block}`, (error, localRes, block) => { if (error) { logger.log('error', @@ -124,7 +127,8 @@ module.exports = function transactionAPI(router) { }); }); } else if (req.query.address) { - db.blocks.getBlock( + // Get txs by address + db.blocks.getBestHeight( {}, { height: 1 }, 1, @@ -173,10 +177,11 @@ module.exports = function transactionAPI(router) { }); }); } else { + // Get last n txs db.txs.getTransactions( {}, {}, - 50, + MAX_TXS, (err, txs) => { if (err) { logger.log('err', @@ -221,7 +226,7 @@ module.exports = function transactionAPI(router) { router.post('/tx/send', (req, res) => { const rawtx = req.body.rawtx || ''; request.post({ - url: `http://${config.bcoin_http}:${config.bcoin['http-port']}/broadcast`, + url: `${API_URL}/broadcast`, body: { tx: rawtx }, json: true, }, (err, localRes, body) => { diff --git a/server/lib/db/blocks.js b/server/lib/db/blocks.js index e3da8fe..563620c 100644 --- a/server/lib/db/blocks.js +++ b/server/lib/db/blocks.js @@ -54,7 +54,19 @@ function getBlock(params, options, limit, cb) { }); } +function getBestHeight(cb) { + getBlock({}, {}, 1, (err, block) => { + if (err) { + logger.log('error', + `getBlock: ${err.err}`); + return cb(err); + } + return cb(null, block.height); + }); +} + module.exports = { getBlock, getBlocks, + getBestHeight, }; From 2f4533e0573dc65b9651036e83662049ba4007f3 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 01:13:30 -0400 Subject: [PATCH 27/44] getBestHeight added to db api --- server/lib/api/address.js | 22 +++++++++++----------- server/lib/api/currency.js | 1 + server/lib/api/index.js | 5 ++--- server/lib/api/transaction.js | 19 +++++-------------- server/lib/db/blocks.js | 2 +- 5 files changed, 20 insertions(+), 29 deletions(-) diff --git a/server/lib/api/address.js b/server/lib/api/address.js index 47cc1dd..c2a9e1a 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -8,7 +8,7 @@ module.exports = function AddressAPI(router) { router.get('/addr/:addr', (req, res) => { const addr = req.params.addr || ''; // Get Bcoin data - request(`${API_URL}/tx/address/${addr}`, + return request(`${API_URL}/tx/address/${addr}`, (error, bcoinRes, txs) => { if (error) { logger.log('error', @@ -41,17 +41,17 @@ module.exports = function AddressAPI(router) { // 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, + 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, + txApperances: txs.length, }); }); }); diff --git a/server/lib/api/currency.js b/server/lib/api/currency.js index 0e55496..80af7aa 100644 --- a/server/lib/api/currency.js +++ b/server/lib/api/currency.js @@ -16,6 +16,7 @@ setInterval(() => { getRate(); }, refreshInterval); +// Make the request to the remote API function getRate() { request(config.api.ticker_url, (err, res, body) => { if (err) { diff --git a/server/lib/api/index.js b/server/lib/api/index.js index c871e61..3bc0d98 100644 --- a/server/lib/api/index.js +++ b/server/lib/api/index.js @@ -7,9 +7,7 @@ const api = express.Router(); const cors = require('./cors'); app.use(cors); - -app.use(bodyParser.urlencoded({ extended: false })) - +app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); // Serve insight ui front end from root dir public folder @@ -47,6 +45,7 @@ app.use((req, res) => { }); }); +// Socket server const server = require('http').Server(app); const io = require('socket.io')(server); diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 007c313..4cc6306 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -10,10 +10,7 @@ module.exports = function transactionAPI(router) { // Txs by txid router.get('/tx/:txid', (req, res) => { // Get max block height for calculating confirmations - db.blocks.getBlock( - {}, - { height: 1 }, - 1, + db.blocks.getBestHeight( (err, block) => { if (err) { logger.log('err', err); @@ -22,7 +19,7 @@ module.exports = function transactionAPI(router) { const height = block.height; // Bcoin transaction data - request(`${API_URL}/tx/${req.params.txid}`, (error, localRes, tx) => { + return request(`${API_URL}/tx/${req.params.txid}`, (error, localRes, tx) => { if (error) { logger.log('error', `${error}`); @@ -73,10 +70,7 @@ module.exports = function transactionAPI(router) { const rangeEnd = rangeStart + MAX_TXS; // get txs for blockhash if (req.query.block) { - db.blocks.getBlock( - {}, - { height: 1 }, - 1, + db.blocks.getBestHeight( (err, block) => { if (err) { logger.log('err', err); @@ -84,7 +78,7 @@ module.exports = function transactionAPI(router) { } const height = block.height; // Get Bcoin data - request(`${API_URL}/block/${req.query.block}`, (error, localRes, block) => { + return request(`${API_URL}/block/${req.query.block}`, (error, localRes, block) => { if (error) { logger.log('error', `${error}`); @@ -129,9 +123,6 @@ module.exports = function transactionAPI(router) { } else if (req.query.address) { // Get txs by address db.blocks.getBestHeight( - {}, - { height: 1 }, - 1, (err, block) => { if (err) { logger.log('err', err); @@ -141,7 +132,7 @@ module.exports = function transactionAPI(router) { const height = block.height; const addr = req.query.address || ''; - request(`${API_URL}/tx/address/${req.query.address}`, (error, localRes, txs) => { + return request(`${API_URL}/tx/address/${req.query.address}`, (error, localRes, txs) => { if (error) { logger.log('error', `${error}`); diff --git a/server/lib/db/blocks.js b/server/lib/db/blocks.js index 563620c..d0c9a53 100644 --- a/server/lib/db/blocks.js +++ b/server/lib/db/blocks.js @@ -53,7 +53,7 @@ function getBlock(params, options, limit, cb) { return cb(null, blocks[0]); }); } - +// Highest known height function getBestHeight(cb) { getBlock({}, {}, 1, (err, block) => { if (err) { From 06b0e108d5cd2aaf04e75bae3a9f1c302685e713 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 01:47:30 -0400 Subject: [PATCH 28/44] socket reworking to reduce coupling --- server/.eslintrc.json | 3 +- server/index.js | 2 +- server/lib/api/index.js | 8 +--- server/lib/api/socket.js | 74 +++++++++++++++++++++++++++----- server/lib/node/index.js | 30 ++----------- server/lib/parser/address.js | 23 ---------- server/lib/parser/index.js | 4 -- server/lib/parser/transaction.js | 38 ---------------- 8 files changed, 70 insertions(+), 112 deletions(-) delete mode 100644 server/lib/parser/address.js delete mode 100644 server/lib/parser/transaction.js diff --git a/server/.eslintrc.json b/server/.eslintrc.json index e66ade8..e642fb4 100644 --- a/server/.eslintrc.json +++ b/server/.eslintrc.json @@ -8,6 +8,7 @@ "no-multi-spaces": 0, "no-use-before-define": 1, "object-shorthand": 1, - "key-spacing": 0 + "key-spacing": 0, + "no-plusplus": 0 } } \ No newline at end of file diff --git a/server/index.js b/server/index.js index 98f8aab..23d3125 100644 --- a/server/index.js +++ b/server/index.js @@ -1,7 +1,7 @@ const Bcoin = require('./lib/node'); const config = require('./config'); const logger = require('./lib/logger'); -const Api = require('./lib/api').server; +const Api = require('./lib/api'); const db = require('./lib/db'); logger.log('debug', diff --git a/server/lib/api/index.js b/server/lib/api/index.js index 3bc0d98..9de150c 100644 --- a/server/lib/api/index.js +++ b/server/lib/api/index.js @@ -47,11 +47,5 @@ app.use((req, res) => { // Socket server const server = require('http').Server(app); -const io = require('socket.io')(server); -const SocketAPI = require('./socket')(io); - -module.exports = { - server, - io, -}; +module.exports = server; diff --git a/server/lib/api/socket.js b/server/lib/api/socket.js index ad8577c..7882fc7 100644 --- a/server/lib/api/socket.js +++ b/server/lib/api/socket.js @@ -1,16 +1,68 @@ -// Change to have services push blocks/txs -module.exports = function addressrouter(io) { - io.on('connection', (socket) => { - socket.on('subscribe', (data) => { - }); +const server = require('.'); +const io = require('socket.io')(server); - 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, tx) => { + tx = tx.toJSON(); + + const valB = (tx.value || tx.valueOut.value || 0) / 1e8; + + return sum + valB; + }, 0), + }); +} + +module.exports = { + io, + processBlock, + emitBlock, + emitTx, }; diff --git a/server/lib/node/index.js b/server/lib/node/index.js index 2c9cc88..266f5b1 100644 --- a/server/lib/node/index.js +++ b/server/lib/node/index.js @@ -1,22 +1,11 @@ const FullNode = require('bcoin/lib/node/fullnode'); const logger = require('../../lib/logger'); const BlockParser = require('../parser').Block; -const TxParser = require('../parser').Transaction; -const addrParser = require('../parser').Address; const config = require('../../config'); -const io = require('../api').io; - -// Reverse how sockets are working +const socket = require('../../lib/api/socket'); 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() { node.open() .then(() => { @@ -27,21 +16,8 @@ function start() { }); node.chain.on('connect', (entry, block) => { - BlockParser.parse(entry, block); - TxParser.parse(entry, block.txs); - addrParser.parse(entry, block.txs); - - if (refreshBlocks) { - refreshBlocks = false; - io.sockets.emit('block', { - hash: block.toJSON().hash, - }); - } - }); - - node.pool.on('peer', (peer) => { - + socket.processBlock(entry, block); }); node.on('error', (err) => { @@ -50,7 +26,7 @@ function start() { }); node.mempool.on('tx', (tx) => { - + socket.emitTx(tx); }); } diff --git a/server/lib/parser/address.js b/server/lib/parser/address.js deleted file mode 100644 index f53c35c..0000000 --- a/server/lib/parser/address.js +++ /dev/null @@ -1,23 +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) => { - - tx.outputs.forEach((output) => { - const outputJSON = output.toJSON(); - }); - - tx.inputs.forEach((input) => { - const inputJSON = input.toJSON(); - }); - }); -} - -module.exports = { - parse, -}; diff --git a/server/lib/parser/index.js b/server/lib/parser/index.js index b73118c..13c2d5b 100644 --- a/server/lib/parser/index.js +++ b/server/lib/parser/index.js @@ -1,9 +1,5 @@ const Block = require('./block'); -const Transaction = require('./transaction'); -const Address = require('./address'); module.exports = { Block, - Transaction, - Address, }; diff --git a/server/lib/parser/transaction.js b/server/lib/parser/transaction.js deleted file mode 100644 index 8549ffe..0000000 --- a/server/lib/parser/transaction.js +++ /dev/null @@ -1,38 +0,0 @@ -const TxModel = require('../../models/transaction'); -const InputModel = require('../../models/input'); -const OutputModel = require('../../models/output'); -const config = require('../../config'); -const util = require('../../lib/util'); -const logger = require('../logger'); -const io = require('../api').io; - -const socketThrottle = 250; -let counter = 0; - -function parse(entry, txs) { - txs.forEach((tx) => { - const txJSON = tx.toJSON(); - - counter++; - - if (counter % socketThrottle === 0) { - - counter = 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), - }); - } - }); -} - -module.exports = { - parse, -}; From ad39b6337c59e9b9e1de7ea3ec9b588928c2a315 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 02:02:19 -0400 Subject: [PATCH 29/44] comment updates --- server/lib/api/transaction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 4cc6306..941de45 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -68,7 +68,7 @@ module.exports = function transactionAPI(router) { const pageNum = parseInt(req.query.pageNum) || 0; const rangeStart = pageNum * MAX_TXS; const rangeEnd = rangeStart + MAX_TXS; - // get txs for blockhash + // get txs for blockhash, start with best height to calc confirmations if (req.query.block) { db.blocks.getBestHeight( (err, block) => { @@ -121,7 +121,7 @@ module.exports = function transactionAPI(router) { }); }); } else if (req.query.address) { - // Get txs by address + // Get txs by address, start with best height to calc confirmations db.blocks.getBestHeight( (err, block) => { if (err) { From 958fc79204ebc7cc84de257eee42dd18e8a523b0 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 10:40:50 -0400 Subject: [PATCH 30/44] Fix return val in transactions.js for bestBlockHeight --- server/lib/api/transaction.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 941de45..5bc41bc 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -11,13 +11,12 @@ module.exports = function transactionAPI(router) { router.get('/tx/:txid', (req, res) => { // Get max block height for calculating confirmations db.blocks.getBestHeight( - (err, block) => { + (err, blockHeight) => { if (err) { logger.log('err', err); return res.status(404).send(); } - - const height = block.height; + const height = blockHeight; // Bcoin transaction data return request(`${API_URL}/tx/${req.params.txid}`, (error, localRes, tx) => { if (error) { @@ -70,13 +69,14 @@ module.exports = function transactionAPI(router) { const rangeEnd = rangeStart + MAX_TXS; // get txs for blockhash, start with best height to calc confirmations if (req.query.block) { + db.blocks.getBestHeight( - (err, block) => { + (err, blockHeight) => { if (err) { logger.log('err', err); return res.status(404).send(); } - const height = block.height; + const height = blockHeight; // Get Bcoin data return request(`${API_URL}/block/${req.query.block}`, (error, localRes, block) => { if (error) { @@ -123,13 +123,13 @@ module.exports = function transactionAPI(router) { } else if (req.query.address) { // Get txs by address, start with best height to calc confirmations db.blocks.getBestHeight( - (err, block) => { + (err, blockHeight) => { if (err) { logger.log('err', err); return res.status(404).send(); } - const height = block.height; + const height = blockHeight; const addr = req.query.address || ''; return request(`${API_URL}/tx/address/${req.query.address}`, (error, localRes, txs) => { From 515884bf63868ca945014c478b758bef0000c7dc Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 12:39:12 -0400 Subject: [PATCH 31/44] Fix isCoinBase typo and add comments --- server/lib/api/transaction.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 5bc41bc..0f17186 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -24,6 +24,7 @@ module.exports = function transactionAPI(router) { `${error}`); return res.status(404).send(); } + // Catch JSON errors try { tx = JSON.parse(tx); } catch (e) { @@ -36,6 +37,8 @@ module.exports = function transactionAPI(router) { 'No results found'); return res.status(404).send(); } + + // Return UI JSON return res.send({ txid: tx.hash, version: tx.version, @@ -56,13 +59,16 @@ module.exports = function transactionAPI(router) { }, 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) || 0; const rangeStart = pageNum * MAX_TXS; @@ -83,6 +89,7 @@ module.exports = function transactionAPI(router) { logger.log('error', `${error}`); } + // Catch JSON errors try { block = JSON.parse(block); } catch (e) { @@ -95,6 +102,7 @@ module.exports = function transactionAPI(router) { `${'No tx results'}`); res.status(404).send(); } + // Setup UI JSON const totalPages = Math.ceil(block.txs.length / MAX_TXS); block.txs = block.txs.slice(rangeStart, rangeEnd); @@ -132,12 +140,13 @@ module.exports = function transactionAPI(router) { const height = blockHeight; const addr = req.query.address || ''; - return request(`${API_URL}/tx/address/${req.query.address}`, (error, localRes, txs) => { + return request(`${API_URL}/tx/address/${addr}`, (error, localRes, txs) => { if (error) { logger.log('error', `${error}`); return res.status(404).send(); } + // Catch JSON errors try { txs = JSON.parse(txs); } catch (e) { @@ -145,6 +154,7 @@ module.exports = function transactionAPI(router) { `${e}`); return res.status(404).send(); } + // Setup UI JSON return res.send({ pagesTotal: 1, txs: txs.map(tx => ({ From e5b608809bfb3af576ace8a0a61f529443f9987a Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 12:49:37 -0400 Subject: [PATCH 32/44] Fix Coinbase in blocks and address. Remove console log from compiled js (removed from temp previousl) --- server/lib/api/transaction.js | 4 +-- server/public/js/main.js | 3 +- server/public/js/main.min.js | 2 +- server/public/views/transaction/tx.html | 38 ++++++++++++------------- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 0f17186..697ffb2 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -123,7 +123,7 @@ module.exports = function transactionAPI(router) { }, value: output.value / 1e8, })), - output: tx.outputs, + isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', })), }); }); @@ -172,7 +172,7 @@ module.exports = function transactionAPI(router) { }, value: output.value / 1e8, })), - output: tx.outputs, + isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', })), }); }); diff --git a/server/public/js/main.js b/server/public/js/main.js index c07a895..215639d 100644 --- a/server/public/js/main.js +++ b/server/public/js/main.js @@ -1123,7 +1123,6 @@ ScopedSocket.prototype.emit = function(event, data, callback) { angular.module('insight.socket').factory('getSocket', function($rootScope) { - console.log('init my socket'); var socket = io.connect(null, { 'reconnect': true, 'reconnection delay': 500, @@ -1394,4 +1393,4 @@ angular.module('insight').run(['gettextCatalog', function (gettextCatalog) { gettextCatalog.setStrings('es', {"(Input unconfirmed)":"(Entrada sin confirmar)","404 Page not found :(":"404 Página no encontrada :(","insight is an open-source Bitcoin blockchain explorer with complete REST and websocket APIs that can be used for writing web wallets and other apps that need more advanced blockchain queries than provided by bitcoind RPC. Check out the source code.":"insight es un explorador de bloques de Bitcoin open-source con un completo conjunto de REST y APIs de websockets que pueden ser usadas para escribir monederos de Bitcoins y otras aplicaciones que requieran consultar un explorador de bloques. Obtén el código en el repositorio abierto de Github.","insight is still in development, so be sure to report any bugs and provide feedback for improvement at our github issue tracker.":"insight esta en desarrollo aún, por ello agradecemos que nos reporten errores o sugerencias para mejorar el software. Github issue tracker.","About":"Acerca de","Address":"Dirección","Age":"Edad","Application Status":"Estado de la Aplicación","Best Block":"Mejor Bloque","Bitcoin node information":"Información del nodo Bitcoin","Block":"Bloque","Block Reward":"Bloque Recompensa","Blocks":"Bloques","Bytes Serialized":"Bytes Serializados","Can't connect to bitcoind to get live updates from the p2p network. (Tried connecting to bitcoind at {{host}}:{{port}} and failed.)":"No se pudo conectar a bitcoind para obtener actualizaciones en vivo de la red p2p. (Se intentó conectar a bitcoind de {{host}}:{{port}} y falló.)","Can't connect to insight server. Attempting to reconnect...":"No se pudo conectar al servidor insight. Intentando re-conectar...","Can't connect to internet. Please, check your connection.":"No se pudo conectar a Internet. Por favor, verifique su conexión.","Complete":"Completado","Confirmations":"Confirmaciones","Conn":"Con","Connections to other nodes":"Conexiones a otros nodos","Current Blockchain Tip (insight)":"Actual Blockchain Tip (insight)","Current Sync Status":"Actual Estado de Sincronización","Details":"Detalles","Difficulty":"Dificultad","Double spent attempt detected. From tx:":"Intento de doble gasto detectado. De la transacción:","Error!":"¡Error!","Fee":"Tasa","Final Balance":"Balance Final","Finish Date":"Fecha Final","Go to home":"Volver al Inicio","Hash Serialized":"Hash Serializado","Height":"Altura","Included in Block":"Incluido en el Bloque","Incoherence in levelDB detected:":"Detectada una incoherencia en levelDB:","Info Errors":"Errores de Información","Initial Block Chain Height":"Altura de la Cadena en Bloque Inicial","Input":"Entrada","Last Block":"Último Bloque","Last Block Hash (Bitcoind)":"Último Bloque Hash (Bitcoind)","Latest Blocks":"Últimos Bloques","Latest Transactions":"Últimas Transacciones","Loading Address Information":"Cargando Información de la Dirección","Loading Block Information":"Cargando Información del Bloque","Loading Selected Date...":"Cargando Fecha Seleccionada...","Loading Transaction Details":"Cargando Detalles de la Transacción","Loading Transactions...":"Cargando Transacciones...","Loading...":"Cargando...","Mined Time":"Hora de Minado","Mined by":"Minado por","Mining Difficulty":"Dificultad de Minado","Next Block":"Próximo Bloque","No Inputs (Newly Generated Coins)":"Sin Entradas (Monedas Recién Generadas)","No blocks yet.":"No hay bloques aún.","No matching records found!":"¡No se encontraron registros coincidentes!","No. Transactions":"Nro. de Transacciones","Number Of Transactions":"Número de Transacciones","Output":"Salida","Powered by":"Funciona con","Previous Block":"Bloque Anterior","Protocol version":"Versión del protocolo","Proxy setting":"Opción de proxy","Received Time":"Hora de Recibido","Redirecting...":"Redireccionando...","Search for block, transaction or address":"Buscar bloques, transacciones o direcciones","See all blocks":"Ver todos los bloques","Show Transaction Output data":"Mostrar dato de Salida de la Transacción","Show all":"Mostrar todos","Show input":"Mostrar entrada","Show less":"Ver menos","Show more":"Ver más","Size":"Tamaño","Size (bytes)":"Tamaño (bytes)","Skipped Blocks (previously synced)":"Bloques Saltados (previamente sincronizado)","Start Date":"Fecha de Inicio","Status":"Estado","Summary":"Resumen","Summary confirmed":"Resumen confirmados","Sync Progress":"Proceso de Sincronización","Sync Status":"Estado de Sincronización","Sync Type":"Tipo de Sincronización","Synced Blocks":"Bloques Sincornizados","Testnet":"Red de prueba","There are no transactions involving this address.":"No hay transacciones para esta dirección","Time Offset":"Desplazamiento de hora","Timestamp":"Fecha y hora","Today":"Hoy","Total Amount":"Cantidad Total","Total Received":"Total Recibido","Total Sent":"Total Enviado","Transaction":"Transacción","Transaction Output Set Information":"Información del Conjunto de Salida de la Transacción","Transaction Outputs":"Salidas de la Transacción","Transactions":"Transacciones","Type":"Tipo","Unconfirmed":"Sin confirmar","Unconfirmed Transaction!":"¡Transacción sin confirmar!","Unconfirmed Txs Balance":"Balance sin confirmar","Value Out":"Valor de Salida","Version":"Versión","Waiting for blocks...":"Esperando bloques...","Waiting for transactions...":"Esperando transacciones...","by date.":"por fecha.","first seen at":"Visto a","mined":"minado","mined on:":"minado el:","Waiting for blocks":"Esperando bloques"}); gettextCatalog.setStrings('ja', {"(Input unconfirmed)":"(入力は未検証です)","404 Page not found :(":"404 ページがみつかりません (´・ω・`)","insight is an open-source Bitcoin blockchain explorer with complete REST and websocket APIs that can be used for writing web wallets and other apps that need more advanced blockchain queries than provided by bitcoind RPC. Check out the source code.":"insightは、bitcoind RPCの提供するものよりも詳細なブロックチェインへの問い合わせを必要とするウェブウォレットやその他のアプリを書くのに使える、完全なRESTおよびwebsocket APIを備えたオープンソースのビットコインブロックエクスプローラです。ソースコードを確認","insight is still in development, so be sure to report any bugs and provide feedback for improvement at our github issue tracker.":"insightは現在開発中です。githubのissueトラッカにてバグの報告や改善案の提案をお願いします。","About":"はじめに","Address":"アドレス","Age":"生成後経過時間","An error occured in the verification process.":"検証過程でエラーが発生しました。","An error occured:
{{error}}":"エラーが発生しました:
{{error}}","Application Status":"アプリケーションの状態","Best Block":"最良ブロック","Bitcoin comes with a way of signing arbitrary messages.":"Bitcoinには任意のメッセージを署名する昨日が備わっています。","Bitcoin node information":"Bitcoinノード情報","Block":"ブロック","Block Reward":"ブロック報酬","Blocks":"ブロック","Broadcast Raw Transaction":"生のトランザクションを配信","Bytes Serialized":"シリアライズ後の容量 (バイト)","Can't connect to bitcoind to get live updates from the p2p network. (Tried connecting to bitcoind at {{host}}:{{port}} and failed.)":"P2Pネットワークからライブ情報を取得するためにbitcoindへ接続することができませんでした。({{host}}:{{port}} への接続を試みましたが、失敗しました。)","Can't connect to insight server. Attempting to reconnect...":"insight サーバに接続できません。再接続しています...","Can't connect to internet. Please, check your connection.":"インターネットに接続できません。コネクションを確認してください。","Complete":"完了","Confirmations":"検証数","Conn":"接続数","Connections to other nodes":"他ノードへの接続","Current Blockchain Tip (insight)":"現在のブロックチェインのTip (insight)","Current Sync Status":"現在の同期状況","Details":"詳細","Difficulty":"難易度","Double spent attempt detected. From tx:":"二重支払い攻撃をこのトランザクションから検知しました:","Error message:":"エラーメッセージ:","Error!":"エラー!","Fee":"手数料","Final Balance":"最終残高","Finish Date":"終了日時","Go to home":"ホームへ","Hash Serialized":"シリアライズデータのハッシュ値","Height":"ブロック高","Included in Block":"取り込まれたブロック","Incoherence in levelDB detected:":"levelDBの破損を検知しました:","Info Errors":"エラー情報","Initial Block Chain Height":"起動時のブロック高","Input":"入力","Last Block":"直前のブロック","Last Block Hash (Bitcoind)":"直前のブロックのハッシュ値 (Bitcoind)","Latest Blocks":"最新のブロック","Latest Transactions":"最新のトランザクション","Loading Address Information":"アドレス情報を読み込んでいます","Loading Block Information":"ブロック情報を読み込んでいます","Loading Selected Date...":"選択されたデータを読み込んでいます...","Loading Transaction Details":"トランザクションの詳細を読み込んでいます","Loading Transactions...":"トランザクションを読み込んでいます...","Loading...":"ロード中...","Message":"メッセージ","Mined Time":"採掘時刻","Mined by":"採掘者","Mining Difficulty":"採掘難易度","Next Block":"次のブロック","No Inputs (Newly Generated Coins)":"入力なし (新しく生成されたコイン)","No blocks yet.":"ブロックはありません。","No matching records found!":"一致するレコードはありません!","No. Transactions":"トランザクション数","Number Of Transactions":"トランザクション数","Output":"出力","Powered by":"Powered by","Previous Block":"前のブロック","Protocol version":"プロトコルバージョン","Proxy setting":"プロキシ設定","Raw transaction data":"トランザクションの生データ","Raw transaction data must be a valid hexadecimal string.":"生のトランザクションデータは有効な16進数でなければいけません。","Received Time":"受信時刻","Redirecting...":"リダイレクトしています...","Search for block, transaction or address":"ブロック、トランザクション、アドレスを検索","See all blocks":"すべてのブロックをみる","Send transaction":"トランザクションを送信","Show Transaction Output data":"トランザクションの出力データをみる","Show all":"すべて表示","Show input":"入力を表示","Show less":"隠す","Show more":"表示する","Signature":"署名","Size":"サイズ","Size (bytes)":"サイズ (バイト)","Skipped Blocks (previously synced)":"スキップされたブロック (同期済み)","Start Date":"開始日時","Status":"ステータス","Summary":"概要","Summary confirmed":"サマリ 検証済み","Sync Progress":"同期の進捗状況","Sync Status":"同期ステータス","Sync Type":"同期タイプ","Synced Blocks":"同期されたブロック数","Testnet":"テストネット","The message failed to verify.":"メッセージの検証に失敗しました。","The message is verifiably from {{verification.address}}.":"メッセージは{{verification.address}}により検証されました。","There are no transactions involving this address.":"このアドレスに対するトランザクションはありません。","This form can be used to broadcast a raw transaction in hex format over\n the Bitcoin network.":"このフォームでは、16進数フォーマットの生のトランザクションをBitcoinネットワーク上に配信することができます。","This form can be used to verify that a message comes from\n a specific Bitcoin address.":"このフォームでは、メッセージが特定のBitcoinアドレスから来たかどうかを検証することができます。","Time Offset":"時間オフセット","Timestamp":"タイムスタンプ","Today":"今日","Total Amount":"Bitcoin総量","Total Received":"総入金額","Total Sent":"総送金額","Transaction":"トランザクション","Transaction Output Set Information":"トランザクションの出力セット情報","Transaction Outputs":"トランザクションの出力","Transaction succesfully broadcast.
Transaction id: {{txid}}":"トランザクションの配信に成功しました。
トランザクションID: {{txid}}","Transactions":"トランザクション","Type":"タイプ","Unconfirmed":"未検証","Unconfirmed Transaction!":"未検証のトランザクションです!","Unconfirmed Txs Balance":"未検証トランザクションの残高","Value Out":"出力値","Verify":"検証","Verify signed message":"署名済みメッセージを検証","Version":"バージョン","Waiting for blocks...":"ブロックを待っています...","Waiting for transactions...":"トランザクションを待っています...","by date.":"日毎。","first seen at":"最初に発見された日時","mined":"採掘された","mined on:":"採掘日時:","(Mainchain)":"(メインチェーン)","(Orphaned)":"(孤立したブロック)","Bits":"Bits","Block #{{block.height}}":"ブロック #{{block.height}}","BlockHash":"ブロックのハッシュ値","Blocks
mined on:":"ブロック
採掘日","Coinbase":"コインベース","Hash":"ハッシュ値","LockTime":"ロック時間","Merkle Root":"Merkleルート","Nonce":"Nonce","Ooops!":"おぉっと!","Output is spent":"出力は使用済みです","Output is unspent":"出力は未使用です","Scan":"スキャン","Show/Hide items details":"アイテムの詳細を表示または隠す","Waiting for blocks":"ブロックを待っています","by date. {{detail}} {{before}}":"日時順 {{detail}} {{before}}","scriptSig":"scriptSig","{{tx.confirmations}} Confirmations":"{{tx.confirmations}} 検証"," (Orphaned)":" (孤立したブロック)"," Incoherence in levelDB detected: {{vin.dbError}}":" Incoherence in levelDB detected: {{vin.dbError}}","Waiting for blocks ":"ブロックを待っています "}); /* jshint +W100 */ -}]); \ No newline at end of file +}]); diff --git a/server/public/js/main.min.js b/server/public/js/main.min.js index 187c53d..4de685c 100644 --- a/server/public/js/main.min.js +++ b/server/public/js/main.min.js @@ -1,3 +1,3 @@ /*! insight-ui 0.4.0 */ -var defaultLanguage=localStorage.getItem("insight-language")||"en",defaultCurrency=localStorage.getItem("insight-currency")||"BTC";angular.module("insight",["ngAnimate","ngResource","ngRoute","ngProgress","ui.bootstrap","ui.route","monospaced.qrcode","gettext","angularMoment","insight.system","insight.socket","insight.api","insight.blocks","insight.transactions","insight.address","insight.search","insight.status","insight.connection","insight.currency","insight.messages"]),angular.module("insight.system",[]),angular.module("insight.socket",[]),angular.module("insight.api",[]),angular.module("insight.blocks",[]),angular.module("insight.transactions",[]),angular.module("insight.address",[]),angular.module("insight.search",[]),angular.module("insight.status",[]),angular.module("insight.connection",[]),angular.module("insight.currency",[]),angular.module("insight.messages",[]),angular.module("insight.address").controller("AddressController",function($scope,$rootScope,$routeParams,$location,Global,Address,getSocket){$scope.global=Global;var socket=getSocket($scope),addrStr=$routeParams.addrStr,_startSocket=function(){socket.on("bitcoind/addresstxid",function(data){if(data.address===addrStr){$rootScope.$broadcast("tx",data.txid);var base=document.querySelector("base"),beep=new Audio(base.href+"/sound/transaction.mp3");beep.play()}}),socket.emit("subscribe","bitcoind/addresstxid",[addrStr])},_stopSocket=function(){socket.emit("unsubscribe","bitcoind/addresstxid",[addrStr])};socket.on("connect",function(){_startSocket()}),$scope.$on("$destroy",function(){_stopSocket()}),$scope.params=$routeParams,$scope.findOne=function(){$rootScope.currentAddr=$routeParams.addrStr,_startSocket(),Address.get({addrStr:$routeParams.addrStr},function(address){$rootScope.titleDetail=address.addrStr.substring(0,7)+"...",$rootScope.flashMessage=null,$scope.address=address},function(e){400===e.status?$rootScope.flashMessage="Invalid Address: "+$routeParams.addrStr:503===e.status?$rootScope.flashMessage="Backend Error. "+e.data:$rootScope.flashMessage="Address Not Found",$location.path("/")})}}),angular.module("insight.blocks").controller("BlocksController",function($scope,$rootScope,$routeParams,$location,Global,Block,Blocks,BlockByHeight){$scope.global=Global,$scope.loading=!1,$routeParams.blockHeight&&BlockByHeight.get({blockHeight:$routeParams.blockHeight},function(hash){$location.path("/block/"+hash.blockHash)},function(){$rootScope.flashMessage="Bad Request",$location.path("/")});var _formatTimestamp=function(date){var yyyy=date.getUTCFullYear().toString(),mm=(date.getUTCMonth()+1).toString(),dd=date.getUTCDate().toString();return yyyy+"-"+(mm[1]?mm:"0"+mm[0])+"-"+(dd[1]?dd:"0"+dd[0])};$scope.$watch("dt",function(newValue,oldValue){newValue!==oldValue&&$location.path("/blocks-date/"+_formatTimestamp(newValue))}),$scope.openCalendar=function($event){$event.preventDefault(),$event.stopPropagation(),$scope.opened=!0},$scope.humanSince=function(time){var m=moment.unix(time).startOf("day"),b=moment().startOf("day");return m.max().from(b)},$scope.list=function(){if($scope.loading=!0,$routeParams.blockDate&&($scope.detail="On "+$routeParams.blockDate),$routeParams.startTimestamp){var d=new Date(1e3*$routeParams.startTimestamp),m=d.getMinutes();10>m&&(m="0"+m),$scope.before=" before "+d.getHours()+":"+m}$rootScope.titleDetail=$scope.detail,Blocks.get({blockDate:$routeParams.blockDate,startTimestamp:$routeParams.startTimestamp},function(res){$scope.loading=!1,$scope.blocks=res.blocks,$scope.pagination=res.pagination})},$scope.findOne=function(){$scope.loading=!0,Block.get({blockHash:$routeParams.blockHash},function(block){$rootScope.titleDetail=block.height,$rootScope.flashMessage=null,$scope.loading=!1,$scope.block=block},function(e){400===e.status?$rootScope.flashMessage="Invalid Transaction ID: "+$routeParams.txId:503===e.status?$rootScope.flashMessage="Backend Error. "+e.data:$rootScope.flashMessage="Block Not Found",$location.path("/")})},$scope.params=$routeParams}),angular.module("insight.connection").controller("ConnectionController",function($scope,$window,Status,getSocket,PeerSync){$scope.apiOnline=!0,$scope.serverOnline=!0,$scope.clienteOnline=!0;var socket=getSocket($scope);socket.on("connect",function(){$scope.serverOnline=!0,socket.on("disconnect",function(){$scope.serverOnline=!1})}),$scope.getConnStatus=function(){PeerSync.get({},function(peer){$scope.apiOnline=peer.connected,$scope.host=peer.host,$scope.port=peer.port},function(){$scope.apiOnline=!1})},socket.emit("subscribe","sync"),socket.on("status",function(sync){$scope.sync=sync,$scope.apiOnline="aborted"!==sync.status&&"error"!==sync.status}),$window.addEventListener("offline",function(){$scope.$apply(function(){$scope.clienteOnline=!1})},!0),$window.addEventListener("online",function(){$scope.$apply(function(){$scope.clienteOnline=!0})},!0)}),angular.module("insight.currency").controller("CurrencyController",function($scope,$rootScope,Currency){$rootScope.currency.symbol=defaultCurrency;var _roundFloat=function(x,n){return parseInt(n,10)&&parseFloat(x)||(n=0),Math.round(x*Math.pow(10,n))/Math.pow(10,n)};$rootScope.currency.getConvertion=function(value){if(value=1*value,!isNaN(value)&&"undefined"!=typeof value&&null!==value){if(0===value)return"0 "+this.symbol;var response;return"USD"===this.symbol?response=_roundFloat(value*this.factor,2):"mBTC"===this.symbol?(this.factor=1e3,response=_roundFloat(value*this.factor,5)):"bits"===this.symbol?(this.factor=1e6,response=_roundFloat(value*this.factor,2)):(this.factor=1,response=_roundFloat(value*this.factor,8)),1e-7>response&&(response=response.toFixed(8)),response+" "+this.symbol}return"value error"},$scope.setCurrency=function(currency){$rootScope.currency.symbol=currency,localStorage.setItem("insight-currency",currency),"USD"===currency?Currency.get({},function(res){$rootScope.currency.factor=$rootScope.currency.bitstamp=res.data.bitstamp}):"mBTC"===currency?$rootScope.currency.factor=1e3:"bits"===currency?$rootScope.currency.factor=1e6:$rootScope.currency.factor=1},Currency.get({},function(res){$rootScope.currency.factor=$rootScope.currency.bitstamp=res.data.bitstamp})}),angular.module("insight.system").controller("FooterController",function($scope,$route,$templateCache,gettextCatalog,amMoment,Version){$scope.defaultLanguage=defaultLanguage;var _getVersion=function(){Version.get({},function(res){$scope.version=res.version})};$scope.version=_getVersion(),$scope.availableLanguages=[{name:"Deutsch",isoCode:"de_DE"},{name:"English",isoCode:"en"},{name:"Spanish",isoCode:"es"},{name:"Japanese",isoCode:"ja"}],$scope.setLanguage=function(isoCode){gettextCatalog.currentLanguage=$scope.defaultLanguage=defaultLanguage=isoCode,amMoment.changeLocale(isoCode),localStorage.setItem("insight-language",isoCode);var currentPageTemplate=$route.current.templateUrl;$templateCache.remove(currentPageTemplate),$route.reload()}}),angular.module("insight.system").controller("HeaderController",function($scope,$rootScope,$modal,getSocket,Global,Block){$scope.global=Global,$rootScope.currency={factor:1,bitstamp:0,symbol:"BTC"},$scope.menu=[{title:"Blocks",link:"blocks"},{title:"Status",link:"status"}],$scope.openScannerModal=function(){$modal.open({templateUrl:"scannerModal.html",controller:"ScannerController"})};var _getBlock=function(hash){Block.get({blockHash:hash},function(res){$scope.totalBlocks=res.height})},socket=getSocket($scope);socket.on("connect",function(){socket.emit("subscribe","inv"),socket.on("block",function(block){var blockHash=block.toString();_getBlock(blockHash)})}),$rootScope.isCollapsed=!0});var TRANSACTION_DISPLAYED=10,BLOCKS_DISPLAYED=5;angular.module("insight.system").controller("IndexController",function($scope,Global,getSocket,Blocks){$scope.global=Global;var _getBlocks=function(){Blocks.get({limit:BLOCKS_DISPLAYED},function(res){$scope.blocks=res.blocks,$scope.blocksLength=res.length})},socket=getSocket($scope),_startSocket=function(){socket.emit("subscribe","inv"),socket.on("tx",function(tx){$scope.txs.unshift(tx),parseInt($scope.txs.length,10)>=parseInt(TRANSACTION_DISPLAYED,10)&&($scope.txs=$scope.txs.splice(0,TRANSACTION_DISPLAYED))}),socket.on("block",function(){_getBlocks()})};socket.on("connect",function(){_startSocket()}),$scope.humanSince=function(time){var m=moment.unix(time);return m.max().fromNow()},$scope.index=function(){_getBlocks(),_startSocket()},$scope.txs=[],$scope.blocks=[]}),angular.module("insight.messages").controller("VerifyMessageController",function($scope,$http,Api){$scope.message={address:"",signature:"",message:""},$scope.verification={status:"unverified",result:null,error:null,address:""},$scope.verifiable=function(){return $scope.message.address&&$scope.message.signature&&$scope.message.message},$scope.verify=function(){$scope.verification.status="loading",$scope.verification.address=$scope.message.address,$http.post(Api.apiPrefix+"/messages/verify",$scope.message).success(function(data,status,headers,config){return"boolean"!=typeof data.result?($scope.verification.status="error",void($scope.verification.error=null)):($scope.verification.status="verified",void($scope.verification.result=data.result))}).error(function(data,status,headers,config){$scope.verification.status="error",$scope.verification.error=data})};var unverify=function(){$scope.verification.status="unverified"};$scope.$watch("message.address",unverify),$scope.$watch("message.signature",unverify),$scope.$watch("message.message",unverify)}),angular.module("insight.system").controller("ScannerController",function($scope,$rootScope,$modalInstance,Global){$scope.global=Global;var isMobile={Android:function(){return navigator.userAgent.match(/Android/i)},BlackBerry:function(){return navigator.userAgent.match(/BlackBerry/i)},iOS:function(){return navigator.userAgent.match(/iPhone|iPad|iPod/i)},Opera:function(){return navigator.userAgent.match(/Opera Mini/i)},Windows:function(){return navigator.userAgent.match(/IEMobile/i)},any:function(){return isMobile.Android()||isMobile.BlackBerry()||isMobile.iOS()||isMobile.Opera()||isMobile.Windows()}};navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,window.URL=window.URL||window.webkitURL||window.mozURL||window.msURL,$scope.isMobile=isMobile.any(),$scope.scannerLoading=!1;var cameraInput,video,canvas,$video,context,localMediaStream,$searchInput=angular.element(document.getElementById("search")),_scan=function(evt){if($scope.isMobile){$scope.scannerLoading=!0;var files=evt.target.files;if(1===files.length&&0===files[0].type.indexOf("image/")){var file=files[0],reader=new FileReader;reader.onload=function(theFile){return function(e){var mpImg=new MegaPixImage(file);mpImg.render(canvas,{maxWidth:200,maxHeight:200,orientation:6}),setTimeout(function(){qrcode.width=canvas.width,qrcode.height=canvas.height,qrcode.imagedata=context.getImageData(0,0,qrcode.width,qrcode.height);try{qrcode.decode()}catch(e){alert(e)}},1500)}}(file),reader.readAsDataURL(file)}}else{if(localMediaStream){context.drawImage(video,0,0,300,225);try{qrcode.decode()}catch(e){}}setTimeout(_scan,500)}},_successCallback=function(stream){video.src=window.URL&&window.URL.createObjectURL(stream)||stream,localMediaStream=stream,video.play(),setTimeout(_scan,1e3)},_scanStop=function(){$scope.scannerLoading=!1,$modalInstance.close(),$scope.isMobile||(localMediaStream.stop&&localMediaStream.stop(),localMediaStream=null,video.src="")},_videoError=function(err){console.log("Video Error: "+JSON.stringify(err)),_scanStop()};qrcode.callback=function(data){_scanStop();var str=0===data.indexOf("bitcoin:")?data.substring(8):data;console.log("QR code detected: "+str),$searchInput.val(str).triggerHandler("change").triggerHandler("submit")},$scope.cancel=function(){_scanStop()},$modalInstance.opened.then(function(){$rootScope.isCollapsed=!0,setTimeout(function(){canvas=document.getElementById("qr-canvas"),context=canvas.getContext("2d"),$scope.isMobile?(cameraInput=document.getElementById("qrcode-camera"),cameraInput.addEventListener("change",_scan,!1)):(video=document.getElementById("qrcode-scanner-video"),$video=angular.element(video),canvas.width=300,canvas.height=225,context.clearRect(0,0,300,225),navigator.getUserMedia({video:!0},_successCallback,_videoError))},500)})}),angular.module("insight.search").controller("SearchController",function($scope,$routeParams,$location,$timeout,Global,Block,Transaction,Address,BlockByHeight){$scope.global=Global,$scope.loading=!1;var _badQuery=function(){$scope.badQuery=!0,$timeout(function(){$scope.badQuery=!1},2e3)},_resetSearch=function(){$scope.q="",$scope.loading=!1};$scope.search=function(){var q=$scope.q;$scope.badQuery=!1,$scope.loading=!0,Block.get({blockHash:q},function(){_resetSearch(),$location.path("block/"+q)},function(){Transaction.get({txId:q},function(){_resetSearch(),$location.path("tx/"+q)},function(){Address.get({addrStr:q},function(){_resetSearch(),$location.path("address/"+q)},function(){isFinite(q)?BlockByHeight.get({blockHeight:q},function(hash){_resetSearch(),$location.path("/block/"+hash.blockHash)},function(){$scope.loading=!1,_badQuery()}):($scope.loading=!1,_badQuery())})})})}}),angular.module("insight.status").controller("StatusController",function($scope,$routeParams,$location,Global,Status,Sync,getSocket){$scope.global=Global,$scope.getStatus=function(q){Status.get({q:"get"+q},function(d){$scope.loaded=1,angular.extend($scope,d)},function(e){$scope.error="API ERROR: "+e.data})},$scope.humanSince=function(time){var m=moment.unix(time/1e3);return m.max().fromNow()};var _onSyncUpdate=function(sync){$scope.sync=sync},_startSocket=function(){socket.emit("subscribe","sync"),socket.on("status",function(sync){_onSyncUpdate(sync)})},socket=getSocket($scope);socket.on("connect",function(){_startSocket()}),$scope.getSync=function(){_startSocket(),Sync.get({},function(sync){_onSyncUpdate(sync)},function(e){var err="Could not get sync information"+e.toString();$scope.sync={error:err}})}}),angular.module("insight.transactions").controller("transactionsController",function($scope,$rootScope,$routeParams,$location,Global,Transaction,TransactionsByBlock,TransactionsByAddress){$scope.global=Global,$scope.loading=!1,$scope.loadedBy=null;var pageNum=0,pagesTotal=1,COIN=1e8,_aggregateItems=function(items){if(!items)return[];for(var l=items.length,ret=[],tmp={},u=0,i=0;l>i;i++){var notAddr=!1;if(items[i].scriptSig&&!items[i].addr&&(items[i].addr="Unparsed address ["+u++ +"]",items[i].notAddr=!0,notAddr=!0),items[i].scriptPubKey&&!items[i].scriptPubKey.addresses&&(items[i].scriptPubKey.addresses=["Unparsed address ["+u++ +"]"],items[i].notAddr=!0,notAddr=!0),items[i].scriptPubKey&&items[i].scriptPubKey.addresses.length>1)items[i].addr=items[i].scriptPubKey.addresses.join(","),ret.push(items[i]);else{var addr=items[i].addr||items[i].scriptPubKey&&items[i].scriptPubKey.addresses[0];tmp[addr]||(tmp[addr]={},tmp[addr].valueSat=0,tmp[addr].count=0,tmp[addr].addr=addr,tmp[addr].items=[]),tmp[addr].isSpent=items[i].spentTxId,tmp[addr].doubleSpentTxID=tmp[addr].doubleSpentTxID||items[i].doubleSpentTxID,tmp[addr].doubleSpentIndex=tmp[addr].doubleSpentIndex||items[i].doubleSpentIndex,tmp[addr].dbError=tmp[addr].dbError||items[i].dbError,tmp[addr].valueSat+=Math.round(items[i].value*COIN),tmp[addr].items.push(items[i]),tmp[addr].notAddr=notAddr,items[i].unconfirmedInput&&(tmp[addr].unconfirmedInput=!0),tmp[addr].count++}}return angular.forEach(tmp,function(v){v.value=v.value||parseInt(v.valueSat)/COIN,ret.push(v)}),ret},_processTX=function(tx){tx.vinSimple=_aggregateItems(tx.vin),tx.voutSimple=_aggregateItems(tx.vout)},_paginate=function(data){$scope.loading=!1,pagesTotal=data.pagesTotal,pageNum+=1,data.txs.forEach(function(tx){_processTX(tx),$scope.txs.push(tx)})},_byBlock=function(){TransactionsByBlock.get({block:$routeParams.blockHash,pageNum:pageNum},function(data){_paginate(data)})},_byAddress=function(){TransactionsByAddress.get({address:$routeParams.addrStr,pageNum:pageNum},function(data){_paginate(data)})},_findTx=function(txid){Transaction.get({txId:txid},function(tx){$rootScope.titleDetail=tx.txid.substring(0,7)+"...",$rootScope.flashMessage=null,$scope.tx=tx,_processTX(tx),$scope.txs.unshift(tx)},function(e){400===e.status?$rootScope.flashMessage="Invalid Transaction ID: "+$routeParams.txId:503===e.status?$rootScope.flashMessage="Backend Error. "+e.data:$rootScope.flashMessage="Transaction Not Found",$location.path("/")})};$scope.findThis=function(){_findTx($routeParams.txId)},$scope.load=function(from){$scope.loadedBy=from,$scope.loadMore()},$scope.loadMore=function(){pagesTotal>pageNum&&!$scope.loading&&($scope.loading=!0,"address"===$scope.loadedBy?_byAddress():_byBlock())},(">"==$routeParams.v_type||"<"==$routeParams.v_type)&&($scope.from_vin="<"==$routeParams.v_type?!0:!1,$scope.from_vout=">"==$routeParams.v_type?!0:!1,$scope.v_index=parseInt($routeParams.v_index),$scope.itemsExpanded=!0),$scope.txs=[],$scope.$on("tx",function(event,txid){_findTx(txid)})}),angular.module("insight.transactions").controller("SendRawTransactionController",function($scope,$http,Api){$scope.transaction="",$scope.status="ready",$scope.txid="",$scope.error=null,$scope.formValid=function(){return!!$scope.transaction},$scope.send=function(){var postData={rawtx:$scope.transaction};$scope.status="loading",$http.post(Api.apiPrefix+"/tx/send",postData).success(function(data,status,headers,config){return"string"!=typeof data.txid?($scope.status="error",void($scope.error="The transaction was sent but no transaction id was got back")):($scope.status="sent",void($scope.txid=data.txid))}).error(function(data,status,headers,config){$scope.status="error",data?$scope.error=data:$scope.error="No error message given (connection error?)"})}}),angular.module("insight.address").factory("Address",function($resource,Api){return $resource(Api.apiPrefix+"/addr/:addrStr/?noTxList=1",{addrStr:"@addStr"},{get:{method:"GET",interceptor:{response:function(res){return res.data},responseError:function(res){return 404===res.status?res:void 0}}}})}),angular.module("insight.api").factory("Api",function(){return{apiPrefix:"/insight-api"}}),angular.module("insight.blocks").factory("Block",function($resource,Api){return $resource(Api.apiPrefix+"/block/:blockHash",{blockHash:"@blockHash"},{get:{method:"GET",interceptor:{response:function(res){return res.data},responseError:function(res){return 404===res.status?res:void 0}}}})}).factory("Blocks",function($resource,Api){return $resource(Api.apiPrefix+"/blocks")}).factory("BlockByHeight",function($resource,Api){return $resource(Api.apiPrefix+"/block-index/:blockHeight")}),angular.module("insight.currency").factory("Currency",function($resource,Api){return $resource(Api.apiPrefix+"/currency")}),angular.module("insight.system").factory("Global",[function(){}]).factory("Version",function($resource,Api){return $resource(Api.apiPrefix+"/version")});var ScopedSocket=function(socket,$rootScope){this.socket=socket,this.$rootScope=$rootScope,this.listeners=[]};ScopedSocket.prototype.removeAllListeners=function(opts){opts||(opts={});for(var i=0;i=200?scope.secondaryNavbar=!0:scope.secondaryNavbar=!1,scope.$apply()})}}).directive("whenScrolled",function($window){return{restric:"A",link:function(scope,elm,attr){var pageHeight,clientHeight,scrollPos;$window=angular.element($window);var handler=function(){pageHeight=window.document.documentElement.scrollHeight,clientHeight=window.document.documentElement.clientHeight,scrollPos=window.pageYOffset,pageHeight-(scrollPos+clientHeight)===0&&scope.$apply(attr.whenScrolled)};$window.on("scroll",handler),scope.$on("$destroy",function(){return $window.off("scroll",handler)})}}}).directive("clipCopy",function(){return ZeroClipboard.config({moviePath:"/lib/zeroclipboard/ZeroClipboard.swf",trustedDomains:["*"],allowScriptAccess:"always",forceHandCursor:!0}),{restric:"A",scope:{clipCopy:"=clipCopy"},template:'
Copied!
',link:function(scope,elm){var clip=new ZeroClipboard(elm);clip.on("load",function(client){var onMousedown=function(client){client.setText(scope.clipCopy)};client.on("mousedown",onMousedown),scope.$on("$destroy",function(){client.off("mousedown",onMousedown)})}),clip.on("noFlash wrongflash",function(){return elm.remove()})}}}).directive("focus",function($timeout){return{scope:{trigger:"@focus"},link:function(scope,element){scope.$watch("trigger",function(value){"true"===value&&$timeout(function(){element[0].focus()})})}}}),angular.module("insight").filter("startFrom",function(){return function(input,start){return start=+start,input.slice(start)}}).filter("split",function(){return function(input,delimiter){var delimiter=delimiter||",";return input.split(delimiter)}}),angular.module("insight").config(function($routeProvider){$routeProvider.when("/block/:blockHash",{templateUrl:"views/block.html",title:"Bitcoin Block "}).when("/block-index/:blockHeight",{controller:"BlocksController",templateUrl:"views/redirect.html"}).when("/tx/send",{templateUrl:"views/transaction_sendraw.html",title:"Broadcast Raw Transaction"}).when("/tx/:txId/:v_type?/:v_index?",{templateUrl:"views/transaction.html",title:"Bitcoin Transaction "}).when("/",{templateUrl:"views/index.html",title:"Home"}).when("/blocks",{templateUrl:"views/block_list.html",title:"Bitcoin Blocks solved Today"}).when("/blocks-date/:blockDate/:startTimestamp?",{templateUrl:"views/block_list.html",title:"Bitcoin Blocks solved "}).when("/address/:addrStr",{templateUrl:"views/address.html",title:"Bitcoin Address "}).when("/status",{templateUrl:"views/status.html",title:"Status"}).when("/messages/verify",{templateUrl:"views/messages_verify.html",title:"Verify Message"}).otherwise({templateUrl:"views/404.html",title:"Error"})}),angular.module("insight").config(function($locationProvider){$locationProvider.html5Mode(!0),$locationProvider.hashPrefix("!")}).run(function($rootScope,$route,$location,$routeParams,$anchorScroll,ngProgress,gettextCatalog,amMoment){gettextCatalog.currentLanguage=defaultLanguage,amMoment.changeLocale(defaultLanguage),$rootScope.$on("$routeChangeStart",function(){ngProgress.start()}),$rootScope.$on("$routeChangeSuccess",function(){ngProgress.complete(),$rootScope.titleDetail="",$rootScope.title=$route.current.title,$rootScope.isCollapsed=!0,$rootScope.currentAddr=null,$location.hash($routeParams.scrollTo),$anchorScroll()})}),angular.element(document).ready(function(){}),angular.module("insight").run(["gettextCatalog",function(gettextCatalog){gettextCatalog.setStrings("de_DE",{"(Input unconfirmed)":"(Eingabe unbestätigt)","404 Page not found :(":"404 Seite nicht gefunden :(",'insight is an open-source Bitcoin blockchain explorer with complete REST and websocket APIs that can be used for writing web wallets and other apps that need more advanced blockchain queries than provided by bitcoind RPC. Check out the source code.':'insight ist ein Open Source Bitcoin Blockchain Explorer mit vollständigen REST und Websocket APIs um eigene Wallets oder Applikationen zu implementieren. Hierbei werden fortschrittlichere Abfragen der Blockchain ermöglicht, bei denen die RPC des Bitcoind nicht mehr ausreichen. Der aktuelle Quellcode ist auf Github zu finden.','insight is still in development, so be sure to report any bugs and provide feedback for improvement at our github issue tracker.':'insight befindet sich aktuell noch in der Entwicklung. Bitte sende alle gefundenen Fehler (Bugs) und Feedback zur weiteren Verbesserung an unseren Github Issue Tracker.',About:"Über insight",Address:"Adresse",Age:"Alter","Application Status":"Programmstatus","Best Block":"Bester Block","Bitcoin node information":"Bitcoin-Node Info",Block:"Block","Block Reward":"Belohnung",Blocks:"Blöcke","Bytes Serialized":"Serialisierte Bytes","Can't connect to bitcoind to get live updates from the p2p network. (Tried connecting to bitcoind at {{host}}:{{port}} and failed.)":"Es ist nicht möglich mit Bitcoind zu verbinden um live Aktualisierungen vom P2P Netzwerk zu erhalten. (Verbindungsversuch zu bitcoind an {{host}}:{{port}} ist fehlgeschlagen.)","Can't connect to insight server. Attempting to reconnect...":"Keine Verbindung zum insight-Server möglich. Es wird versucht die Verbindung neu aufzubauen...","Can't connect to internet. Please, check your connection.":"Keine Verbindung zum Internet möglich, bitte Zugangsdaten prüfen.",Complete:"Vollständig",Confirmations:"Bestätigungen",Conn:"Verbindungen","Connections to other nodes":"Verbindungen zu Nodes","Current Blockchain Tip (insight)":"Aktueller Blockchain Tip (insight)","Current Sync Status":"Aktueller Status",Details:"Details",Difficulty:"Schwierigkeit","Double spent attempt detected. From tx:":'Es wurde ein "double Spend" Versuch erkannt.Von tx:',"Error!":"Fehler!",Fee:"Gebühr","Final Balance":"Schlussbilanz","Finish Date":"Fertigstellung","Go to home":"Zur Startseite","Hash Serialized":"Hash Serialisiert",Height:"Höhe","Included in Block":"Eingefügt in Block","Incoherence in levelDB detected:":"Es wurde eine Zusammenhangslosigkeit in der LevelDB festgestellt:","Info Errors":"Fehlerbeschreibung","Initial Block Chain Height":"Ursprüngliche Blockchain Höhe",Input:"Eingänge","Last Block":"Letzter Block","Last Block Hash (Bitcoind)":"Letzter Hash (Bitcoind)","Latest Blocks":"Letzte Blöcke","Latest Transactions":"Letzte Transaktionen","Loading Address Information":"Lade Adressinformationen","Loading Block Information":"Lade Blockinformation","Loading Selected Date...":"Lade gewähltes Datum...","Loading Transaction Details":"Lade Transaktionsdetails","Loading Transactions...":"Lade Transaktionen...","Loading...":"Lade...","Mined Time":"Block gefunden (Mining)","Mined by":"Gefunden von","Mining Difficulty":"Schwierigkeitgrad","Next Block":"Nächster Block","No Inputs (Newly Generated Coins)":"Keine Eingänge (Neu generierte Coins)","No blocks yet.":"Keine Blöcke bisher.","No matching records found!":"Keine passenden Einträge gefunden!","No. Transactions":"Anzahl Transaktionen","Number Of Transactions":"Anzahl der Transaktionen",Output:"Ausgänge","Powered by":"Powered by","Previous Block":"Letzter Block","Protocol version":"Protokollversion","Proxy setting":"Proxyeinstellung","Received Time":"Eingangszeitpunkt","Redirecting...":"Umleitung...","Search for block, transaction or address":"Suche Block, Transaktion oder Adresse","See all blocks":"Alle Blöcke anzeigen","Show Transaction Output data":"Zeige Abgänge","Show all":"Zeige Alles","Show input":"Zeige Eingänge","Show less":"Weniger anzeigen","Show more":"Mehr anzeigen",Size:"Größe","Size (bytes)":"Größe (bytes)","Skipped Blocks (previously synced)":"Verworfene Blöcke (bereits syncronisiert)","Start Date":"Startdatum",Status:"Status",Summary:"Zusammenfassung","Summary confirmed":"Zusammenfassung bestätigt","Sync Progress":"Fortschritt","Sync Status":"Syncronisation","Sync Type":"Art der Syncronisation","Synced Blocks":"Syncronisierte Blöcke",Testnet:"Testnet aktiv","There are no transactions involving this address.":"Es gibt keine Transaktionen zu dieser Adressse","Time Offset":"Zeitoffset zu UTC",Timestamp:"Zeitstempel",Today:"Heute","Total Amount":"Gesamtsumme","Total Received":"Insgesamt empfangen","Total Sent":"Insgesamt gesendet",Transaction:"Transaktion","Transaction Output Set Information":"Transaktions Abgänge","Transaction Outputs":"Abgänge",Transactions:"Transaktionen",Type:"Typ",Unconfirmed:"Unbestätigt","Unconfirmed Transaction!":"Unbestätigte Transaktion!","Unconfirmed Txs Balance":"Unbestätigtes Guthaben","Value Out":"Wert",Version:"Version","Waiting for blocks...":"Warte auf Blöcke...","Waiting for transactions...":"Warte auf Transaktionen...","by date.":"nach Datum.","first seen at":"zuerst gesehen am",mined:"gefunden","mined on:":"vom:","Waiting for blocks":"Warte auf Blöcke"}),gettextCatalog.setStrings("es",{"(Input unconfirmed)":"(Entrada sin confirmar)","404 Page not found :(":"404 Página no encontrada :(",'insight is an open-source Bitcoin blockchain explorer with complete REST and websocket APIs that can be used for writing web wallets and other apps that need more advanced blockchain queries than provided by bitcoind RPC. Check out the source code.':'insight es un explorador de bloques de Bitcoin open-source con un completo conjunto de REST y APIs de websockets que pueden ser usadas para escribir monederos de Bitcoins y otras aplicaciones que requieran consultar un explorador de bloques. Obtén el código en el repositorio abierto de Github.','insight is still in development, so be sure to report any bugs and provide feedback for improvement at our github issue tracker.':'insight esta en desarrollo aún, por ello agradecemos que nos reporten errores o sugerencias para mejorar el software. Github issue tracker.', +var defaultLanguage=localStorage.getItem("insight-language")||"en",defaultCurrency=localStorage.getItem("insight-currency")||"BTC";angular.module("insight",["ngAnimate","ngResource","ngRoute","ngProgress","ui.bootstrap","ui.route","monospaced.qrcode","gettext","angularMoment","insight.system","insight.socket","insight.api","insight.blocks","insight.transactions","insight.address","insight.search","insight.status","insight.connection","insight.currency","insight.messages"]),angular.module("insight.system",[]),angular.module("insight.socket",[]),angular.module("insight.api",[]),angular.module("insight.blocks",[]),angular.module("insight.transactions",[]),angular.module("insight.address",[]),angular.module("insight.search",[]),angular.module("insight.status",[]),angular.module("insight.connection",[]),angular.module("insight.currency",[]),angular.module("insight.messages",[]),angular.module("insight.address").controller("AddressController",function($scope,$rootScope,$routeParams,$location,Global,Address,getSocket){$scope.global=Global;var socket=getSocket($scope),addrStr=$routeParams.addrStr,_startSocket=function(){socket.on("bitcoind/addresstxid",function(data){if(data.address===addrStr){$rootScope.$broadcast("tx",data.txid);var base=document.querySelector("base"),beep=new Audio(base.href+"/sound/transaction.mp3");beep.play()}}),socket.emit("subscribe","bitcoind/addresstxid",[addrStr])},_stopSocket=function(){socket.emit("unsubscribe","bitcoind/addresstxid",[addrStr])};socket.on("connect",function(){_startSocket()}),$scope.$on("$destroy",function(){_stopSocket()}),$scope.params=$routeParams,$scope.findOne=function(){$rootScope.currentAddr=$routeParams.addrStr,_startSocket(),Address.get({addrStr:$routeParams.addrStr},function(address){$rootScope.titleDetail=address.addrStr.substring(0,7)+"...",$rootScope.flashMessage=null,$scope.address=address},function(e){400===e.status?$rootScope.flashMessage="Invalid Address: "+$routeParams.addrStr:503===e.status?$rootScope.flashMessage="Backend Error. "+e.data:$rootScope.flashMessage="Address Not Found",$location.path("/")})}}),angular.module("insight.blocks").controller("BlocksController",function($scope,$rootScope,$routeParams,$location,Global,Block,Blocks,BlockByHeight){$scope.global=Global,$scope.loading=!1,$routeParams.blockHeight&&BlockByHeight.get({blockHeight:$routeParams.blockHeight},function(hash){$location.path("/block/"+hash.blockHash)},function(){$rootScope.flashMessage="Bad Request",$location.path("/")});var _formatTimestamp=function(date){var yyyy=date.getUTCFullYear().toString(),mm=(date.getUTCMonth()+1).toString(),dd=date.getUTCDate().toString();return yyyy+"-"+(mm[1]?mm:"0"+mm[0])+"-"+(dd[1]?dd:"0"+dd[0])};$scope.$watch("dt",function(newValue,oldValue){newValue!==oldValue&&$location.path("/blocks-date/"+_formatTimestamp(newValue))}),$scope.openCalendar=function($event){$event.preventDefault(),$event.stopPropagation(),$scope.opened=!0},$scope.humanSince=function(time){var m=moment.unix(time).startOf("day"),b=moment().startOf("day");return m.max().from(b)},$scope.list=function(){if($scope.loading=!0,$routeParams.blockDate&&($scope.detail="On "+$routeParams.blockDate),$routeParams.startTimestamp){var d=new Date(1e3*$routeParams.startTimestamp),m=d.getMinutes();10>m&&(m="0"+m),$scope.before=" before "+d.getHours()+":"+m}$rootScope.titleDetail=$scope.detail,Blocks.get({blockDate:$routeParams.blockDate,startTimestamp:$routeParams.startTimestamp},function(res){$scope.loading=!1,$scope.blocks=res.blocks,$scope.pagination=res.pagination})},$scope.findOne=function(){$scope.loading=!0,Block.get({blockHash:$routeParams.blockHash},function(block){$rootScope.titleDetail=block.height,$rootScope.flashMessage=null,$scope.loading=!1,$scope.block=block},function(e){400===e.status?$rootScope.flashMessage="Invalid Transaction ID: "+$routeParams.txId:503===e.status?$rootScope.flashMessage="Backend Error. "+e.data:$rootScope.flashMessage="Block Not Found",$location.path("/")})},$scope.params=$routeParams}),angular.module("insight.connection").controller("ConnectionController",function($scope,$window,Status,getSocket,PeerSync){$scope.apiOnline=!0,$scope.serverOnline=!0,$scope.clienteOnline=!0;var socket=getSocket($scope);socket.on("connect",function(){$scope.serverOnline=!0,socket.on("disconnect",function(){$scope.serverOnline=!1})}),$scope.getConnStatus=function(){PeerSync.get({},function(peer){$scope.apiOnline=peer.connected,$scope.host=peer.host,$scope.port=peer.port},function(){$scope.apiOnline=!1})},socket.emit("subscribe","sync"),socket.on("status",function(sync){$scope.sync=sync,$scope.apiOnline="aborted"!==sync.status&&"error"!==sync.status}),$window.addEventListener("offline",function(){$scope.$apply(function(){$scope.clienteOnline=!1})},!0),$window.addEventListener("online",function(){$scope.$apply(function(){$scope.clienteOnline=!0})},!0)}),angular.module("insight.currency").controller("CurrencyController",function($scope,$rootScope,Currency){$rootScope.currency.symbol=defaultCurrency;var _roundFloat=function(x,n){return parseInt(n,10)&&parseFloat(x)||(n=0),Math.round(x*Math.pow(10,n))/Math.pow(10,n)};$rootScope.currency.getConvertion=function(value){if(value=1*value,!isNaN(value)&&"undefined"!=typeof value&&null!==value){if(0===value)return"0 "+this.symbol;var response;return"USD"===this.symbol?response=_roundFloat(value*this.factor,2):"mBTC"===this.symbol?(this.factor=1e3,response=_roundFloat(value*this.factor,5)):"bits"===this.symbol?(this.factor=1e6,response=_roundFloat(value*this.factor,2)):(this.factor=1,response=_roundFloat(value*this.factor,8)),1e-7>response&&(response=response.toFixed(8)),response+" "+this.symbol}return"value error"},$scope.setCurrency=function(currency){$rootScope.currency.symbol=currency,localStorage.setItem("insight-currency",currency),"USD"===currency?Currency.get({},function(res){$rootScope.currency.factor=$rootScope.currency.bitstamp=res.data.bitstamp}):"mBTC"===currency?$rootScope.currency.factor=1e3:"bits"===currency?$rootScope.currency.factor=1e6:$rootScope.currency.factor=1},Currency.get({},function(res){$rootScope.currency.factor=$rootScope.currency.bitstamp=res.data.bitstamp})}),angular.module("insight.system").controller("FooterController",function($scope,$route,$templateCache,gettextCatalog,amMoment,Version){$scope.defaultLanguage=defaultLanguage;var _getVersion=function(){Version.get({},function(res){$scope.version=res.version})};$scope.version=_getVersion(),$scope.availableLanguages=[{name:"Deutsch",isoCode:"de_DE"},{name:"English",isoCode:"en"},{name:"Spanish",isoCode:"es"},{name:"Japanese",isoCode:"ja"}],$scope.setLanguage=function(isoCode){gettextCatalog.currentLanguage=$scope.defaultLanguage=defaultLanguage=isoCode,amMoment.changeLocale(isoCode),localStorage.setItem("insight-language",isoCode);var currentPageTemplate=$route.current.templateUrl;$templateCache.remove(currentPageTemplate),$route.reload()}}),angular.module("insight.system").controller("HeaderController",function($scope,$rootScope,$modal,getSocket,Global,Block){$scope.global=Global,$rootScope.currency={factor:1,bitstamp:0,symbol:"BTC"},$scope.menu=[{title:"Blocks",link:"blocks"},{title:"Status",link:"status"}],$scope.openScannerModal=function(){$modal.open({templateUrl:"scannerModal.html",controller:"ScannerController"})};var _getBlock=function(hash){Block.get({blockHash:hash},function(res){$scope.totalBlocks=res.height})},socket=getSocket($scope);socket.on("connect",function(){socket.emit("subscribe","inv"),socket.on("block",function(block){var blockHash=block.toString();_getBlock(blockHash)})}),$rootScope.isCollapsed=!0});var TRANSACTION_DISPLAYED=10,BLOCKS_DISPLAYED=5;angular.module("insight.system").controller("IndexController",function($scope,Global,getSocket,Blocks){$scope.global=Global;var _getBlocks=function(){Blocks.get({limit:BLOCKS_DISPLAYED},function(res){$scope.blocks=res.blocks,$scope.blocksLength=res.length})},socket=getSocket($scope),_startSocket=function(){socket.emit("subscribe","inv"),socket.on("tx",function(tx){$scope.txs.unshift(tx),parseInt($scope.txs.length,10)>=parseInt(TRANSACTION_DISPLAYED,10)&&($scope.txs=$scope.txs.splice(0,TRANSACTION_DISPLAYED))}),socket.on("block",function(){_getBlocks()})};socket.on("connect",function(){_startSocket()}),$scope.humanSince=function(time){var m=moment.unix(time);return m.max().fromNow()},$scope.index=function(){_getBlocks(),_startSocket()},$scope.txs=[],$scope.blocks=[]}),angular.module("insight.messages").controller("VerifyMessageController",function($scope,$http,Api){$scope.message={address:"",signature:"",message:""},$scope.verification={status:"unverified",result:null,error:null,address:""},$scope.verifiable=function(){return $scope.message.address&&$scope.message.signature&&$scope.message.message},$scope.verify=function(){$scope.verification.status="loading",$scope.verification.address=$scope.message.address,$http.post(Api.apiPrefix+"/messages/verify",$scope.message).success(function(data,status,headers,config){return"boolean"!=typeof data.result?($scope.verification.status="error",void($scope.verification.error=null)):($scope.verification.status="verified",void($scope.verification.result=data.result))}).error(function(data,status,headers,config){$scope.verification.status="error",$scope.verification.error=data})};var unverify=function(){$scope.verification.status="unverified"};$scope.$watch("message.address",unverify),$scope.$watch("message.signature",unverify),$scope.$watch("message.message",unverify)}),angular.module("insight.system").controller("ScannerController",function($scope,$rootScope,$modalInstance,Global){$scope.global=Global;var isMobile={Android:function(){return navigator.userAgent.match(/Android/i)},BlackBerry:function(){return navigator.userAgent.match(/BlackBerry/i)},iOS:function(){return navigator.userAgent.match(/iPhone|iPad|iPod/i)},Opera:function(){return navigator.userAgent.match(/Opera Mini/i)},Windows:function(){return navigator.userAgent.match(/IEMobile/i)},any:function(){return isMobile.Android()||isMobile.BlackBerry()||isMobile.iOS()||isMobile.Opera()||isMobile.Windows()}};navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,window.URL=window.URL||window.webkitURL||window.mozURL||window.msURL,$scope.isMobile=isMobile.any(),$scope.scannerLoading=!1;var cameraInput,video,canvas,$video,context,localMediaStream,$searchInput=angular.element(document.getElementById("search")),_scan=function(evt){if($scope.isMobile){$scope.scannerLoading=!0;var files=evt.target.files;if(1===files.length&&0===files[0].type.indexOf("image/")){var file=files[0],reader=new FileReader;reader.onload=function(theFile){return function(e){var mpImg=new MegaPixImage(file);mpImg.render(canvas,{maxWidth:200,maxHeight:200,orientation:6}),setTimeout(function(){qrcode.width=canvas.width,qrcode.height=canvas.height,qrcode.imagedata=context.getImageData(0,0,qrcode.width,qrcode.height);try{qrcode.decode()}catch(e){alert(e)}},1500)}}(file),reader.readAsDataURL(file)}}else{if(localMediaStream){context.drawImage(video,0,0,300,225);try{qrcode.decode()}catch(e){}}setTimeout(_scan,500)}},_successCallback=function(stream){video.src=window.URL&&window.URL.createObjectURL(stream)||stream,localMediaStream=stream,video.play(),setTimeout(_scan,1e3)},_scanStop=function(){$scope.scannerLoading=!1,$modalInstance.close(),$scope.isMobile||(localMediaStream.stop&&localMediaStream.stop(),localMediaStream=null,video.src="")},_videoError=function(err){console.log("Video Error: "+JSON.stringify(err)),_scanStop()};qrcode.callback=function(data){_scanStop();var str=0===data.indexOf("bitcoin:")?data.substring(8):data;console.log("QR code detected: "+str),$searchInput.val(str).triggerHandler("change").triggerHandler("submit")},$scope.cancel=function(){_scanStop()},$modalInstance.opened.then(function(){$rootScope.isCollapsed=!0,setTimeout(function(){canvas=document.getElementById("qr-canvas"),context=canvas.getContext("2d"),$scope.isMobile?(cameraInput=document.getElementById("qrcode-camera"),cameraInput.addEventListener("change",_scan,!1)):(video=document.getElementById("qrcode-scanner-video"),$video=angular.element(video),canvas.width=300,canvas.height=225,context.clearRect(0,0,300,225),navigator.getUserMedia({video:!0},_successCallback,_videoError))},500)})}),angular.module("insight.search").controller("SearchController",function($scope,$routeParams,$location,$timeout,Global,Block,Transaction,Address,BlockByHeight){$scope.global=Global,$scope.loading=!1;var _badQuery=function(){$scope.badQuery=!0,$timeout(function(){$scope.badQuery=!1},2e3)},_resetSearch=function(){$scope.q="",$scope.loading=!1};$scope.search=function(){var q=$scope.q;$scope.badQuery=!1,$scope.loading=!0,Block.get({blockHash:q},function(){_resetSearch(),$location.path("block/"+q)},function(){Transaction.get({txId:q},function(){_resetSearch(),$location.path("tx/"+q)},function(){Address.get({addrStr:q},function(){_resetSearch(),$location.path("address/"+q)},function(){isFinite(q)?BlockByHeight.get({blockHeight:q},function(hash){_resetSearch(),$location.path("/block/"+hash.blockHash)},function(){$scope.loading=!1,_badQuery()}):($scope.loading=!1,_badQuery())})})})}}),angular.module("insight.status").controller("StatusController",function($scope,$routeParams,$location,Global,Status,Sync,getSocket){$scope.global=Global,$scope.getStatus=function(q){Status.get({q:"get"+q},function(d){$scope.loaded=1,angular.extend($scope,d)},function(e){$scope.error="API ERROR: "+e.data})},$scope.humanSince=function(time){var m=moment.unix(time/1e3);return m.max().fromNow()};var _onSyncUpdate=function(sync){$scope.sync=sync},_startSocket=function(){socket.emit("subscribe","sync"),socket.on("status",function(sync){_onSyncUpdate(sync)})},socket=getSocket($scope);socket.on("connect",function(){_startSocket()}),$scope.getSync=function(){_startSocket(),Sync.get({},function(sync){_onSyncUpdate(sync)},function(e){var err="Could not get sync information"+e.toString();$scope.sync={error:err}})}}),angular.module("insight.transactions").controller("transactionsController",function($scope,$rootScope,$routeParams,$location,Global,Transaction,TransactionsByBlock,TransactionsByAddress){$scope.global=Global,$scope.loading=!1,$scope.loadedBy=null;var pageNum=0,pagesTotal=1,COIN=1e8,_aggregateItems=function(items){if(!items)return[];for(var l=items.length,ret=[],tmp={},u=0,i=0;l>i;i++){var notAddr=!1;if(items[i].scriptSig&&!items[i].addr&&(items[i].addr="Unparsed address ["+u++ +"]",items[i].notAddr=!0,notAddr=!0),items[i].scriptPubKey&&!items[i].scriptPubKey.addresses&&(items[i].scriptPubKey.addresses=["Unparsed address ["+u++ +"]"],items[i].notAddr=!0,notAddr=!0),items[i].scriptPubKey&&items[i].scriptPubKey.addresses.length>1)items[i].addr=items[i].scriptPubKey.addresses.join(","),ret.push(items[i]);else{var addr=items[i].addr||items[i].scriptPubKey&&items[i].scriptPubKey.addresses[0];tmp[addr]||(tmp[addr]={},tmp[addr].valueSat=0,tmp[addr].count=0,tmp[addr].addr=addr,tmp[addr].items=[]),tmp[addr].isSpent=items[i].spentTxId,tmp[addr].doubleSpentTxID=tmp[addr].doubleSpentTxID||items[i].doubleSpentTxID,tmp[addr].doubleSpentIndex=tmp[addr].doubleSpentIndex||items[i].doubleSpentIndex,tmp[addr].dbError=tmp[addr].dbError||items[i].dbError,tmp[addr].valueSat+=Math.round(items[i].value*COIN),tmp[addr].items.push(items[i]),tmp[addr].notAddr=notAddr,items[i].unconfirmedInput&&(tmp[addr].unconfirmedInput=!0),tmp[addr].count++}}return angular.forEach(tmp,function(v){v.value=v.value||parseInt(v.valueSat)/COIN,ret.push(v)}),ret},_processTX=function(tx){tx.vinSimple=_aggregateItems(tx.vin),tx.voutSimple=_aggregateItems(tx.vout)},_paginate=function(data){$scope.loading=!1,pagesTotal=data.pagesTotal,pageNum+=1,data.txs.forEach(function(tx){_processTX(tx),$scope.txs.push(tx)})},_byBlock=function(){TransactionsByBlock.get({block:$routeParams.blockHash,pageNum:pageNum},function(data){_paginate(data)})},_byAddress=function(){TransactionsByAddress.get({address:$routeParams.addrStr,pageNum:pageNum},function(data){_paginate(data)})},_findTx=function(txid){Transaction.get({txId:txid},function(tx){$rootScope.titleDetail=tx.txid.substring(0,7)+"...",$rootScope.flashMessage=null,$scope.tx=tx,_processTX(tx),$scope.txs.unshift(tx)},function(e){400===e.status?$rootScope.flashMessage="Invalid Transaction ID: "+$routeParams.txId:503===e.status?$rootScope.flashMessage="Backend Error. "+e.data:$rootScope.flashMessage="Transaction Not Found",$location.path("/")})};$scope.findThis=function(){_findTx($routeParams.txId)},$scope.load=function(from){$scope.loadedBy=from,$scope.loadMore()},$scope.loadMore=function(){pagesTotal>pageNum&&!$scope.loading&&($scope.loading=!0,"address"===$scope.loadedBy?_byAddress():_byBlock())},(">"==$routeParams.v_type||"<"==$routeParams.v_type)&&($scope.from_vin="<"==$routeParams.v_type?!0:!1,$scope.from_vout=">"==$routeParams.v_type?!0:!1,$scope.v_index=parseInt($routeParams.v_index),$scope.itemsExpanded=!0),$scope.txs=[],$scope.$on("tx",function(event,txid){_findTx(txid)})}),angular.module("insight.transactions").controller("SendRawTransactionController",function($scope,$http,Api){$scope.transaction="",$scope.status="ready",$scope.txid="",$scope.error=null,$scope.formValid=function(){return!!$scope.transaction},$scope.send=function(){var postData={rawtx:$scope.transaction};$scope.status="loading",$http.post(Api.apiPrefix+"/tx/send",postData).success(function(data,status,headers,config){return"string"!=typeof data.txid?($scope.status="error",void($scope.error="The transaction was sent but no transaction id was got back")):($scope.status="sent",void($scope.txid=data.txid))}).error(function(data,status,headers,config){$scope.status="error",data?$scope.error=data:$scope.error="No error message given (connection error?)"})}}),angular.module("insight.address").factory("Address",function($resource,Api){return $resource(Api.apiPrefix+"/addr/:addrStr/?noTxList=1",{addrStr:"@addStr"},{get:{method:"GET",interceptor:{response:function(res){return res.data},responseError:function(res){return 404===res.status?res:void 0}}}})}),angular.module("insight.api").factory("Api",function(){return{apiPrefix:"/insight-api"}}),angular.module("insight.blocks").factory("Block",function($resource,Api){return $resource(Api.apiPrefix+"/block/:blockHash",{blockHash:"@blockHash"},{get:{method:"GET",interceptor:{response:function(res){return res.data},responseError:function(res){return 404===res.status?res:void 0}}}})}).factory("Blocks",function($resource,Api){return $resource(Api.apiPrefix+"/blocks")}).factory("BlockByHeight",function($resource,Api){return $resource(Api.apiPrefix+"/block-index/:blockHeight")}),angular.module("insight.currency").factory("Currency",function($resource,Api){return $resource(Api.apiPrefix+"/currency")}),angular.module("insight.system").factory("Global",[function(){}]).factory("Version",function($resource,Api){return $resource(Api.apiPrefix+"/version")});var ScopedSocket=function(socket,$rootScope){this.socket=socket,this.$rootScope=$rootScope,this.listeners=[]};ScopedSocket.prototype.removeAllListeners=function(opts){opts||(opts={});for(var i=0;i=200?scope.secondaryNavbar=!0:scope.secondaryNavbar=!1,scope.$apply()})}}).directive("whenScrolled",function($window){return{restric:"A",link:function(scope,elm,attr){var pageHeight,clientHeight,scrollPos;$window=angular.element($window);var handler=function(){pageHeight=window.document.documentElement.scrollHeight,clientHeight=window.document.documentElement.clientHeight,scrollPos=window.pageYOffset,pageHeight-(scrollPos+clientHeight)===0&&scope.$apply(attr.whenScrolled)};$window.on("scroll",handler),scope.$on("$destroy",function(){return $window.off("scroll",handler)})}}}).directive("clipCopy",function(){return ZeroClipboard.config({moviePath:"/lib/zeroclipboard/ZeroClipboard.swf",trustedDomains:["*"],allowScriptAccess:"always",forceHandCursor:!0}),{restric:"A",scope:{clipCopy:"=clipCopy"},template:'
Copied!
',link:function(scope,elm){var clip=new ZeroClipboard(elm);clip.on("load",function(client){var onMousedown=function(client){client.setText(scope.clipCopy)};client.on("mousedown",onMousedown),scope.$on("$destroy",function(){client.off("mousedown",onMousedown)})}),clip.on("noFlash wrongflash",function(){return elm.remove()})}}}).directive("focus",function($timeout){return{scope:{trigger:"@focus"},link:function(scope,element){scope.$watch("trigger",function(value){"true"===value&&$timeout(function(){element[0].focus()})})}}}),angular.module("insight").filter("startFrom",function(){return function(input,start){return start=+start,input.slice(start)}}).filter("split",function(){return function(input,delimiter){var delimiter=delimiter||",";return input.split(delimiter)}}),angular.module("insight").config(function($routeProvider){$routeProvider.when("/block/:blockHash",{templateUrl:"views/block.html",title:"Bitcoin Block "}).when("/block-index/:blockHeight",{controller:"BlocksController",templateUrl:"views/redirect.html"}).when("/tx/send",{templateUrl:"views/transaction_sendraw.html",title:"Broadcast Raw Transaction"}).when("/tx/:txId/:v_type?/:v_index?",{templateUrl:"views/transaction.html",title:"Bitcoin Transaction "}).when("/",{templateUrl:"views/index.html",title:"Home"}).when("/blocks",{templateUrl:"views/block_list.html",title:"Bitcoin Blocks solved Today"}).when("/blocks-date/:blockDate/:startTimestamp?",{templateUrl:"views/block_list.html",title:"Bitcoin Blocks solved "}).when("/address/:addrStr",{templateUrl:"views/address.html",title:"Bitcoin Address "}).when("/status",{templateUrl:"views/status.html",title:"Status"}).when("/messages/verify",{templateUrl:"views/messages_verify.html",title:"Verify Message"}).otherwise({templateUrl:"views/404.html",title:"Error"})}),angular.module("insight").config(function($locationProvider){$locationProvider.html5Mode(!0),$locationProvider.hashPrefix("!")}).run(function($rootScope,$route,$location,$routeParams,$anchorScroll,ngProgress,gettextCatalog,amMoment){gettextCatalog.currentLanguage=defaultLanguage,amMoment.changeLocale(defaultLanguage),$rootScope.$on("$routeChangeStart",function(){ngProgress.start()}),$rootScope.$on("$routeChangeSuccess",function(){ngProgress.complete(),$rootScope.titleDetail="",$rootScope.title=$route.current.title,$rootScope.isCollapsed=!0,$rootScope.currentAddr=null,$location.hash($routeParams.scrollTo),$anchorScroll()})}),angular.element(document).ready(function(){}),angular.module("insight").run(["gettextCatalog",function(gettextCatalog){gettextCatalog.setStrings("de_DE",{"(Input unconfirmed)":"(Eingabe unbestätigt)","404 Page not found :(":"404 Seite nicht gefunden :(",'insight is an open-source Bitcoin blockchain explorer with complete REST and websocket APIs that can be used for writing web wallets and other apps that need more advanced blockchain queries than provided by bitcoind RPC. Check out the source code.':'insight ist ein Open Source Bitcoin Blockchain Explorer mit vollständigen REST und Websocket APIs um eigene Wallets oder Applikationen zu implementieren. Hierbei werden fortschrittlichere Abfragen der Blockchain ermöglicht, bei denen die RPC des Bitcoind nicht mehr ausreichen. Der aktuelle Quellcode ist auf Github zu finden.','insight is still in development, so be sure to report any bugs and provide feedback for improvement at our github issue tracker.':'insight befindet sich aktuell noch in der Entwicklung. Bitte sende alle gefundenen Fehler (Bugs) und Feedback zur weiteren Verbesserung an unseren Github Issue Tracker.',About:"Über insight",Address:"Adresse",Age:"Alter","Application Status":"Programmstatus","Best Block":"Bester Block","Bitcoin node information":"Bitcoin-Node Info",Block:"Block","Block Reward":"Belohnung",Blocks:"Blöcke","Bytes Serialized":"Serialisierte Bytes","Can't connect to bitcoind to get live updates from the p2p network. (Tried connecting to bitcoind at {{host}}:{{port}} and failed.)":"Es ist nicht möglich mit Bitcoind zu verbinden um live Aktualisierungen vom P2P Netzwerk zu erhalten. (Verbindungsversuch zu bitcoind an {{host}}:{{port}} ist fehlgeschlagen.)","Can't connect to insight server. Attempting to reconnect...":"Keine Verbindung zum insight-Server möglich. Es wird versucht die Verbindung neu aufzubauen...","Can't connect to internet. Please, check your connection.":"Keine Verbindung zum Internet möglich, bitte Zugangsdaten prüfen.",Complete:"Vollständig",Confirmations:"Bestätigungen",Conn:"Verbindungen","Connections to other nodes":"Verbindungen zu Nodes","Current Blockchain Tip (insight)":"Aktueller Blockchain Tip (insight)","Current Sync Status":"Aktueller Status",Details:"Details",Difficulty:"Schwierigkeit","Double spent attempt detected. From tx:":'Es wurde ein "double Spend" Versuch erkannt.Von tx:',"Error!":"Fehler!",Fee:"Gebühr","Final Balance":"Schlussbilanz","Finish Date":"Fertigstellung","Go to home":"Zur Startseite","Hash Serialized":"Hash Serialisiert",Height:"Höhe","Included in Block":"Eingefügt in Block","Incoherence in levelDB detected:":"Es wurde eine Zusammenhangslosigkeit in der LevelDB festgestellt:","Info Errors":"Fehlerbeschreibung","Initial Block Chain Height":"Ursprüngliche Blockchain Höhe",Input:"Eingänge","Last Block":"Letzter Block","Last Block Hash (Bitcoind)":"Letzter Hash (Bitcoind)","Latest Blocks":"Letzte Blöcke","Latest Transactions":"Letzte Transaktionen","Loading Address Information":"Lade Adressinformationen","Loading Block Information":"Lade Blockinformation","Loading Selected Date...":"Lade gewähltes Datum...","Loading Transaction Details":"Lade Transaktionsdetails","Loading Transactions...":"Lade Transaktionen...","Loading...":"Lade...","Mined Time":"Block gefunden (Mining)","Mined by":"Gefunden von","Mining Difficulty":"Schwierigkeitgrad","Next Block":"Nächster Block","No Inputs (Newly Generated Coins)":"Keine Eingänge (Neu generierte Coins)","No blocks yet.":"Keine Blöcke bisher.","No matching records found!":"Keine passenden Einträge gefunden!","No. Transactions":"Anzahl Transaktionen","Number Of Transactions":"Anzahl der Transaktionen",Output:"Ausgänge","Powered by":"Powered by","Previous Block":"Letzter Block","Protocol version":"Protokollversion","Proxy setting":"Proxyeinstellung","Received Time":"Eingangszeitpunkt","Redirecting...":"Umleitung...","Search for block, transaction or address":"Suche Block, Transaktion oder Adresse","See all blocks":"Alle Blöcke anzeigen","Show Transaction Output data":"Zeige Abgänge","Show all":"Zeige Alles","Show input":"Zeige Eingänge","Show less":"Weniger anzeigen","Show more":"Mehr anzeigen",Size:"Größe","Size (bytes)":"Größe (bytes)","Skipped Blocks (previously synced)":"Verworfene Blöcke (bereits syncronisiert)","Start Date":"Startdatum",Status:"Status",Summary:"Zusammenfassung","Summary confirmed":"Zusammenfassung bestätigt","Sync Progress":"Fortschritt","Sync Status":"Syncronisation","Sync Type":"Art der Syncronisation","Synced Blocks":"Syncronisierte Blöcke",Testnet:"Testnet aktiv","There are no transactions involving this address.":"Es gibt keine Transaktionen zu dieser Adressse","Time Offset":"Zeitoffset zu UTC",Timestamp:"Zeitstempel",Today:"Heute","Total Amount":"Gesamtsumme","Total Received":"Insgesamt empfangen","Total Sent":"Insgesamt gesendet",Transaction:"Transaktion","Transaction Output Set Information":"Transaktions Abgänge","Transaction Outputs":"Abgänge",Transactions:"Transaktionen",Type:"Typ",Unconfirmed:"Unbestätigt","Unconfirmed Transaction!":"Unbestätigte Transaktion!","Unconfirmed Txs Balance":"Unbestätigtes Guthaben","Value Out":"Wert",Version:"Version","Waiting for blocks...":"Warte auf Blöcke...","Waiting for transactions...":"Warte auf Transaktionen...","by date.":"nach Datum.","first seen at":"zuerst gesehen am",mined:"gefunden","mined on:":"vom:","Waiting for blocks":"Warte auf Blöcke"}),gettextCatalog.setStrings("es",{"(Input unconfirmed)":"(Entrada sin confirmar)","404 Page not found :(":"404 Página no encontrada :(",'insight is an open-source Bitcoin blockchain explorer with complete REST and websocket APIs that can be used for writing web wallets and other apps that need more advanced blockchain queries than provided by bitcoind RPC. Check out the source code.':'insight es un explorador de bloques de Bitcoin open-source con un completo conjunto de REST y APIs de websockets que pueden ser usadas para escribir monederos de Bitcoins y otras aplicaciones que requieran consultar un explorador de bloques. Obtén el código en el repositorio abierto de Github.','insight is still in development, so be sure to report any bugs and provide feedback for improvement at our github issue tracker.':'insight esta en desarrollo aún, por ello agradecemos que nos reporten errores o sugerencias para mejorar el software. Github issue tracker.', About:"Acerca de",Address:"Dirección",Age:"Edad","Application Status":"Estado de la Aplicación","Best Block":"Mejor Bloque","Bitcoin node information":"Información del nodo Bitcoin",Block:"Bloque","Block Reward":"Bloque Recompensa",Blocks:"Bloques","Bytes Serialized":"Bytes Serializados","Can't connect to bitcoind to get live updates from the p2p network. (Tried connecting to bitcoind at {{host}}:{{port}} and failed.)":"No se pudo conectar a bitcoind para obtener actualizaciones en vivo de la red p2p. (Se intentó conectar a bitcoind de {{host}}:{{port}} y falló.)","Can't connect to insight server. Attempting to reconnect...":"No se pudo conectar al servidor insight. Intentando re-conectar...","Can't connect to internet. Please, check your connection.":"No se pudo conectar a Internet. Por favor, verifique su conexión.",Complete:"Completado",Confirmations:"Confirmaciones",Conn:"Con","Connections to other nodes":"Conexiones a otros nodos","Current Blockchain Tip (insight)":"Actual Blockchain Tip (insight)","Current Sync Status":"Actual Estado de Sincronización",Details:"Detalles",Difficulty:"Dificultad","Double spent attempt detected. From tx:":"Intento de doble gasto detectado. De la transacción:","Error!":"¡Error!",Fee:"Tasa","Final Balance":"Balance Final","Finish Date":"Fecha Final","Go to home":"Volver al Inicio","Hash Serialized":"Hash Serializado",Height:"Altura","Included in Block":"Incluido en el Bloque","Incoherence in levelDB detected:":"Detectada una incoherencia en levelDB:","Info Errors":"Errores de Información","Initial Block Chain Height":"Altura de la Cadena en Bloque Inicial",Input:"Entrada","Last Block":"Último Bloque","Last Block Hash (Bitcoind)":"Último Bloque Hash (Bitcoind)","Latest Blocks":"Últimos Bloques","Latest Transactions":"Últimas Transacciones","Loading Address Information":"Cargando Información de la Dirección","Loading Block Information":"Cargando Información del Bloque","Loading Selected Date...":"Cargando Fecha Seleccionada...","Loading Transaction Details":"Cargando Detalles de la Transacción","Loading Transactions...":"Cargando Transacciones...","Loading...":"Cargando...","Mined Time":"Hora de Minado","Mined by":"Minado por","Mining Difficulty":"Dificultad de Minado","Next Block":"Próximo Bloque","No Inputs (Newly Generated Coins)":"Sin Entradas (Monedas Recién Generadas)","No blocks yet.":"No hay bloques aún.","No matching records found!":"¡No se encontraron registros coincidentes!","No. Transactions":"Nro. de Transacciones","Number Of Transactions":"Número de Transacciones",Output:"Salida","Powered by":"Funciona con","Previous Block":"Bloque Anterior","Protocol version":"Versión del protocolo","Proxy setting":"Opción de proxy","Received Time":"Hora de Recibido","Redirecting...":"Redireccionando...","Search for block, transaction or address":"Buscar bloques, transacciones o direcciones","See all blocks":"Ver todos los bloques","Show Transaction Output data":"Mostrar dato de Salida de la Transacción","Show all":"Mostrar todos","Show input":"Mostrar entrada","Show less":"Ver menos","Show more":"Ver más",Size:"Tamaño","Size (bytes)":"Tamaño (bytes)","Skipped Blocks (previously synced)":"Bloques Saltados (previamente sincronizado)","Start Date":"Fecha de Inicio",Status:"Estado",Summary:"Resumen","Summary confirmed":"Resumen confirmados","Sync Progress":"Proceso de Sincronización","Sync Status":"Estado de Sincronización","Sync Type":"Tipo de Sincronización","Synced Blocks":"Bloques Sincornizados",Testnet:"Red de prueba","There are no transactions involving this address.":"No hay transacciones para esta dirección","Time Offset":"Desplazamiento de hora",Timestamp:"Fecha y hora",Today:"Hoy","Total Amount":"Cantidad Total","Total Received":"Total Recibido","Total Sent":"Total Enviado",Transaction:"Transacción","Transaction Output Set Information":"Información del Conjunto de Salida de la Transacción","Transaction Outputs":"Salidas de la Transacción",Transactions:"Transacciones",Type:"Tipo",Unconfirmed:"Sin confirmar","Unconfirmed Transaction!":"¡Transacción sin confirmar!","Unconfirmed Txs Balance":"Balance sin confirmar","Value Out":"Valor de Salida",Version:"Versión","Waiting for blocks...":"Esperando bloques...","Waiting for transactions...":"Esperando transacciones...","by date.":"por fecha.","first seen at":"Visto a",mined:"minado","mined on:":"minado el:","Waiting for blocks":"Esperando bloques"}),gettextCatalog.setStrings("ja",{"(Input unconfirmed)":"(入力は未検証です)","404 Page not found :(":"404 ページがみつかりません (´・ω・`)",'insight is an open-source Bitcoin blockchain explorer with complete REST and websocket APIs that can be used for writing web wallets and other apps that need more advanced blockchain queries than provided by bitcoind RPC. Check out the source code.':'insightは、bitcoind RPCの提供するものよりも詳細なブロックチェインへの問い合わせを必要とするウェブウォレットやその他のアプリを書くのに使える、完全なRESTおよびwebsocket APIを備えたオープンソースのビットコインブロックエクスプローラです。ソースコードを確認','insight is still in development, so be sure to report any bugs and provide feedback for improvement at our github issue tracker.':'insightは現在開発中です。githubのissueトラッカにてバグの報告や改善案の提案をお願いします。',About:"はじめに",Address:"アドレス",Age:"生成後経過時間","An error occured in the verification process.":"検証過程でエラーが発生しました。","An error occured:
{{error}}":"エラーが発生しました:
{{error}}","Application Status":"アプリケーションの状態","Best Block":"最良ブロック","Bitcoin comes with a way of signing arbitrary messages.":"Bitcoinには任意のメッセージを署名する昨日が備わっています。","Bitcoin node information":"Bitcoinノード情報",Block:"ブロック","Block Reward":"ブロック報酬",Blocks:"ブロック","Broadcast Raw Transaction":"生のトランザクションを配信","Bytes Serialized":"シリアライズ後の容量 (バイト)","Can't connect to bitcoind to get live updates from the p2p network. (Tried connecting to bitcoind at {{host}}:{{port}} and failed.)":"P2Pネットワークからライブ情報を取得するためにbitcoindへ接続することができませんでした。({{host}}:{{port}} への接続を試みましたが、失敗しました。)","Can't connect to insight server. Attempting to reconnect...":"insight サーバに接続できません。再接続しています...","Can't connect to internet. Please, check your connection.":"インターネットに接続できません。コネクションを確認してください。",Complete:"完了",Confirmations:"検証数",Conn:"接続数","Connections to other nodes":"他ノードへの接続","Current Blockchain Tip (insight)":"現在のブロックチェインのTip (insight)","Current Sync Status":"現在の同期状況",Details:"詳細",Difficulty:"難易度","Double spent attempt detected. From tx:":"二重支払い攻撃をこのトランザクションから検知しました:","Error message:":"エラーメッセージ:","Error!":"エラー!",Fee:"手数料","Final Balance":"最終残高","Finish Date":"終了日時","Go to home":"ホームへ","Hash Serialized":"シリアライズデータのハッシュ値",Height:"ブロック高","Included in Block":"取り込まれたブロック","Incoherence in levelDB detected:":"levelDBの破損を検知しました:","Info Errors":"エラー情報","Initial Block Chain Height":"起動時のブロック高",Input:"入力","Last Block":"直前のブロック","Last Block Hash (Bitcoind)":"直前のブロックのハッシュ値 (Bitcoind)","Latest Blocks":"最新のブロック","Latest Transactions":"最新のトランザクション","Loading Address Information":"アドレス情報を読み込んでいます","Loading Block Information":"ブロック情報を読み込んでいます","Loading Selected Date...":"選択されたデータを読み込んでいます...","Loading Transaction Details":"トランザクションの詳細を読み込んでいます","Loading Transactions...":"トランザクションを読み込んでいます...","Loading...":"ロード中...",Message:"メッセージ","Mined Time":"採掘時刻","Mined by":"採掘者","Mining Difficulty":"採掘難易度","Next Block":"次のブロック","No Inputs (Newly Generated Coins)":"入力なし (新しく生成されたコイン)","No blocks yet.":"ブロックはありません。","No matching records found!":"一致するレコードはありません!","No. Transactions":"トランザクション数","Number Of Transactions":"トランザクション数",Output:"出力","Powered by":"Powered by","Previous Block":"前のブロック","Protocol version":"プロトコルバージョン","Proxy setting":"プロキシ設定","Raw transaction data":"トランザクションの生データ","Raw transaction data must be a valid hexadecimal string.":"生のトランザクションデータは有効な16進数でなければいけません。","Received Time":"受信時刻","Redirecting...":"リダイレクトしています...","Search for block, transaction or address":"ブロック、トランザクション、アドレスを検索","See all blocks":"すべてのブロックをみる","Send transaction":"トランザクションを送信","Show Transaction Output data":"トランザクションの出力データをみる","Show all":"すべて表示","Show input":"入力を表示","Show less":"隠す","Show more":"表示する",Signature:"署名",Size:"サイズ","Size (bytes)":"サイズ (バイト)","Skipped Blocks (previously synced)":"スキップされたブロック (同期済み)","Start Date":"開始日時",Status:"ステータス",Summary:"概要","Summary confirmed":"サマリ 検証済み","Sync Progress":"同期の進捗状況","Sync Status":"同期ステータス","Sync Type":"同期タイプ","Synced Blocks":"同期されたブロック数",Testnet:"テストネット","The message failed to verify.":"メッセージの検証に失敗しました。","The message is verifiably from {{verification.address}}.":"メッセージは{{verification.address}}により検証されました。","There are no transactions involving this address.":"このアドレスに対するトランザクションはありません。","This form can be used to broadcast a raw transaction in hex format over\n the Bitcoin network.":"このフォームでは、16進数フォーマットの生のトランザクションをBitcoinネットワーク上に配信することができます。","This form can be used to verify that a message comes from\n a specific Bitcoin address.":"このフォームでは、メッセージが特定のBitcoinアドレスから来たかどうかを検証することができます。","Time Offset":"時間オフセット",Timestamp:"タイムスタンプ",Today:"今日","Total Amount":"Bitcoin総量","Total Received":"総入金額","Total Sent":"総送金額",Transaction:"トランザクション","Transaction Output Set Information":"トランザクションの出力セット情報","Transaction Outputs":"トランザクションの出力","Transaction succesfully broadcast.
Transaction id: {{txid}}":"トランザクションの配信に成功しました。
トランザクションID: {{txid}}",Transactions:"トランザクション",Type:"タイプ",Unconfirmed:"未検証","Unconfirmed Transaction!":"未検証のトランザクションです!","Unconfirmed Txs Balance":"未検証トランザクションの残高","Value Out":"出力値",Verify:"検証","Verify signed message":"署名済みメッセージを検証",Version:"バージョン","Waiting for blocks...":"ブロックを待っています...","Waiting for transactions...":"トランザクションを待っています...","by date.":"日毎。","first seen at":"最初に発見された日時",mined:"採掘された","mined on:":"採掘日時:","(Mainchain)":"(メインチェーン)","(Orphaned)":"(孤立したブロック)",Bits:"Bits","Block #{{block.height}}":"ブロック #{{block.height}}",BlockHash:"ブロックのハッシュ値","Blocks
mined on:":"ブロック
採掘日",Coinbase:"コインベース",Hash:"ハッシュ値",LockTime:"ロック時間","Merkle Root":"Merkleルート",Nonce:"Nonce","Ooops!":"おぉっと!","Output is spent":"出力は使用済みです","Output is unspent":"出力は未使用です",Scan:"スキャン","Show/Hide items details":"アイテムの詳細を表示または隠す","Waiting for blocks":"ブロックを待っています","by date. {{detail}} {{before}}":"日時順 {{detail}} {{before}}",scriptSig:"scriptSig","{{tx.confirmations}} Confirmations":"{{tx.confirmations}} 検証",' (Orphaned)':' (孤立したブロック)',' Incoherence in levelDB detected: {{vin.dbError}}':' Incoherence in levelDB detected: {{vin.dbError}}','Waiting for blocks ':'ブロックを待っています '})}]); \ No newline at end of file diff --git a/server/public/views/transaction/tx.html b/server/public/views/transaction/tx.html index da9214c..df61383 100644 --- a/server/public/views/transaction/tx.html +++ b/server/public/views/transaction/tx.html @@ -10,11 +10,11 @@
- first seen at + first seen at
- mined + mined
@@ -44,20 +44,20 @@
(Input unconfirmed)
- + Incoherence in levelDB detected: {{vin.dbError}}
-
- - Double spent attempt detected. From tx: +
+ + Double spent attempt detected. From tx: {{vin.doubleSpentTxID}},{{vin.doubleSpentIndex}}
- -
@@ -75,17 +75,17 @@ {{vin.addr}} {{vin.addr}} -
- +
+ (Input unconfirmed)
-
- +
+ Incoherence in levelDB detected: {{vin.dbError}}
-
- - Double spent attempt detected. From tx: +
+ + Double spent attempt detected. From tx: doubleSpentTxID}}">{{vin.doubleSpentTxID}},{{vin.doubleSpentIndex}}
@@ -106,9 +106,9 @@ Show all
- -
@@ -144,9 +144,9 @@
- -
From c47f58f8e8bc3c5d55b17937bb8dc5e766912f16 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 14:13:18 -0400 Subject: [PATCH 33/44] Clean up eslint complaints --- server/.eslintrc.json | 4 +++- server/index.js | 1 - server/lib/api/address.js | 9 +++++---- server/lib/api/block.js | 4 ++-- server/lib/api/cors.js | 2 +- server/lib/api/currency.js | 12 ++++++++---- server/lib/api/socket.js | 6 +++--- server/lib/api/status.js | 10 +++++----- server/lib/api/transaction.js | 9 ++++----- 9 files changed, 31 insertions(+), 26 deletions(-) diff --git a/server/.eslintrc.json b/server/.eslintrc.json index e642fb4..5139d8a 100644 --- a/server/.eslintrc.json +++ b/server/.eslintrc.json @@ -9,6 +9,8 @@ "no-use-before-define": 1, "object-shorthand": 1, "key-spacing": 0, - "no-plusplus": 0 + "no-plusplus": 0, + "no-unused-vars": 1, + "no-param-reassign": 1 } } \ No newline at end of file diff --git a/server/index.js b/server/index.js index 23d3125..47b2141 100644 --- a/server/index.js +++ b/server/index.js @@ -10,7 +10,6 @@ logger.log('debug', db.connect(config.mongodb.uri, config.mongodb.options); db.connection.once('open', () => { - if (config.start_node) Bcoin.start(); Api.listen(config.api.port, () => { diff --git a/server/lib/api/address.js b/server/lib/api/address.js index c2a9e1a..ed0cab9 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -9,14 +9,15 @@ module.exports = function AddressAPI(router) { const addr = req.params.addr || ''; // Get Bcoin data return request(`${API_URL}/tx/address/${addr}`, - (error, bcoinRes, txs) => { + (error, bcoinRes, bcoinTxs) => { if (error) { logger.log('error', `${error}`); return res.status(404).send({}); } + let txs = {}; try { - txs = JSON.parse(txs); + txs = JSON.parse(bcoinTxs); } catch (e) { logger.log('error', `${e}`); @@ -24,7 +25,7 @@ module.exports = function AddressAPI(router) { } // Sum the matching outputs for every tx - const totalReceived = txs.reduce((sum, tx) => sum + tx.outputs.reduce((sum, output) => { + const totalReceived = txs.reduce((total, tx) => total + tx.outputs.reduce((sum, output) => { if (output.address === req.params.addr) { return sum + output.value; } @@ -32,7 +33,7 @@ module.exports = function AddressAPI(router) { }, 0), 0) || 0; // Sum the matching inputs for every tx - const totalSpent = txs.reduce((sum, tx) => sum + tx.inputs.reduce((sum, input) => { + 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; } diff --git a/server/lib/api/block.js b/server/lib/api/block.js index 9c3af3c..482ea0e 100644 --- a/server/lib/api/block.js +++ b/server/lib/api/block.js @@ -37,7 +37,7 @@ module.exports = function BlockAPI(router) { }); router.get('/blocks', (req, res) => { - const limit = parseInt(req.query.limit) || 100; + const limit = parseInt(req.query.limit, 10) || 100; // Pass Mongo params, fields and limit to db api. db.blocks.getBlocks( {}, @@ -89,7 +89,7 @@ module.exports = function BlockAPI(router) { }); router.get('/block-index/:height', (req, res) => { - const blockHeight = parseInt(req.params.height) || 1; + const blockHeight = parseInt(req.params.height, 10) || 1; // Pass Mongo params, fields and limit to db api. db.blocks.getBlock( { height: blockHeight }, diff --git a/server/lib/api/cors.js b/server/lib/api/cors.js index 1e4bdcd..42c9833 100644 --- a/server/lib/api/cors.js +++ b/server/lib/api/cors.js @@ -1,5 +1,5 @@ module.exports = function (req, res, next) { - let allowed = { + const allowed = { origins: [ '*', diff --git a/server/lib/api/currency.js b/server/lib/api/currency.js index 80af7aa..1f03b89 100644 --- a/server/lib/api/currency.js +++ b/server/lib/api/currency.js @@ -10,11 +10,15 @@ const refreshInterval = config.api.currency_refresh >= 1 ? 60 * 1000; let lastRate = 0; -getRate(); +init(); -setInterval(() => { +function init() { getRate(); -}, refreshInterval); + + setInterval(() => { + getRate(); + }, refreshInterval); +} // Make the request to the remote API function getRate() { @@ -38,7 +42,7 @@ function getRate() { module.exports = function currencyAPI(app) { // Return the ticker price app.get('/currency', (req, res) => { - const data = {} + const data = {}; data[config.api.ticker_prop] = lastRate; res.json({ diff --git a/server/lib/api/socket.js b/server/lib/api/socket.js index 7882fc7..fef4ee8 100644 --- a/server/lib/api/socket.js +++ b/server/lib/api/socket.js @@ -50,10 +50,10 @@ function emitTx(transaction) { const txJSON = transaction.toJSON(); io.sockets.emit('tx', { txid: txJSON.hash, - valueOut: transaction.outputs.reduce((sum, tx) => { - tx = tx.toJSON(); + valueOut: transaction.outputs.reduce((sum, output) => { + output = output.toJSON(); - const valB = (tx.value || tx.valueOut.value || 0) / 1e8; + const valB = (output.value || output.valueOut.value || 0) / 1e8; return sum + valB; }, 0), diff --git a/server/lib/api/status.js b/server/lib/api/status.js index f273e9d..514d5db 100644 --- a/server/lib/api/status.js +++ b/server/lib/api/status.js @@ -53,11 +53,11 @@ module.exports = function statusAPI(router) { return res.status(404).send(err); } if (!status) { - logger.log('err' - `/status getStatus: no Status`); + logger.log('err', + '/status getStatus: no Status'); return res.status(404).send(); } - res.json({ + return res.json({ info: { version: status.version, protocolversion: netCfg.PROTOCOL_VERSION, @@ -88,8 +88,8 @@ module.exports = function statusAPI(router) { '/sync: no status'); return res.status(404).send(); } - res.json({ - status: status.chain.progress === 100 ? 'synced': 'syncing', + 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, diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 697ffb2..fb79b87 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -47,7 +47,7 @@ module.exports = function transactionAPI(router) { locktime: tx.locktime, blockhash: tx.block, fees: tx.fee / 1e8, - confirmations: height - tx.height + 1, + 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 : '', @@ -70,12 +70,11 @@ module.exports = function transactionAPI(router) { // query by address // last n txs router.get('/txs', (req, res) => { - const pageNum = parseInt(req.query.pageNum) || 0; + 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) { - db.blocks.getBestHeight( (err, blockHeight) => { if (err) { @@ -111,7 +110,7 @@ module.exports = function transactionAPI(router) { txs: block.txs.map(tx => ({ txid: tx.hash, fees: tx.fee / 1e8, - confirmations: height - block.height + 1, + 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 : '', @@ -160,7 +159,7 @@ module.exports = function transactionAPI(router) { txs: txs.map(tx => ({ txid: tx.hash, fees: tx.fee / 1e8, - confirmations: height - tx.height + 1, + 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 : '', From 2ac70bc9a2176b0a5f817bc4526ffb71eefc8a2b Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 16:30:33 -0400 Subject: [PATCH 34/44] Fixed Bcoin error returned as data, fixed dead /txs endpoint missing error, added ttl to bcoin requests but large addrs still take too long --- server/config/index.js | 1 + server/lib/api/address.js | 2 + server/lib/api/transaction.js | 289 ++++++++++++++++------------------ server/lib/db/blocks.js | 3 +- server/lib/db/transactions.js | 1 + 5 files changed, 141 insertions(+), 155 deletions(-) diff --git a/server/config/index.js b/server/config/index.js index 2dd1526..4c45558 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -29,6 +29,7 @@ const config = { ticker_prop: 'bitstamp', max_blocks: 72, max_txs: 10, + request_ttl: 100000, }, }; diff --git a/server/lib/api/address.js b/server/lib/api/address.js index ed0cab9..6359400 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -3,12 +3,14 @@ const request = require('request'); const config = require('../../config'); const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; +const TTL = config.api.request_ttl; module.exports = function AddressAPI(router) { router.get('/addr/:addr', (req, res) => { const addr = req.params.addr || ''; // Get Bcoin data return request(`${API_URL}/tx/address/${addr}`, + { timeout: TTL }, (error, bcoinRes, bcoinTxs) => { if (error) { logger.log('error', diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index fb79b87..6948402 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -5,6 +5,7 @@ const db = require('../db'); const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; const MAX_TXS = config.api.max_txs; +const TTL = config.api.request_ttl; module.exports = function transactionAPI(router) { // Txs by txid @@ -18,50 +19,52 @@ module.exports = function transactionAPI(router) { } const height = blockHeight; // Bcoin transaction data - return request(`${API_URL}/tx/${req.params.txid}`, (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 request(`${API_URL}/tx/${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 - 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, - })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', + // 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, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', + }); }); - }); }); }); @@ -83,49 +86,53 @@ module.exports = function transactionAPI(router) { } const height = blockHeight; // Get Bcoin data - return request(`${API_URL}/block/${req.query.block}`, (error, localRes, block) => { - if (error) { - logger.log('error', - `${error}`); - } - // Catch JSON errors - try { - block = JSON.parse(block); - } catch (e) { - logger.log('error', - `${e}`); - return res.status(404).send(); - } - if (!block.txs.length) { - logger.log('error', - `${'No tx results'}`); - res.status(404).send(); - } - // Setup UI JSON - const totalPages = Math.ceil(block.txs.length / MAX_TXS); - block.txs = block.txs.slice(rangeStart, rangeEnd); + 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(); + } - 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, + 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, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', - })), + }); }); - }); }); } else if (req.query.address) { // Get txs by address, start with best height to calc confirmations @@ -139,83 +146,57 @@ module.exports = function transactionAPI(router) { const height = blockHeight; const addr = req.query.address || ''; - return request(`${API_URL}/tx/address/${addr}`, (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(); - } - // 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, + logger.log('debug', + 'Warning: Requesting data from Bcoin, 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, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', - })), + }); }); - }); }); } else { // Get last n txs - db.txs.getTransactions( - {}, - {}, - MAX_TXS, - (err, txs) => { - if (err) { - logger.log('err', - `getTransactions: ${err}`); - res.status(404).send(); - } - return res.json({ - pagesTotal: 1, - txs: txs.map(tx => ({ - txid: tx.hash, - version: tx.version, - locktime: tx.locktime, - vin: tx.inputs.map(input => ({ - coinbase: input.script, - sequence: input.sequence, - n: 0, - })), - vout: tx.outputs.map(output => ({ - value: output.value, - n: 0, - scriptPubKey: { - hex: '', - asm: '', - addresses: [output.address], - type: output.type, - }, - spentTxid: '', - spentIndex: 0, - spentHeight: 0, - })), - })), - }); - }, - ); + return res.status(404).send({ error: 'Block hash or address expected' }); } }); diff --git a/server/lib/db/blocks.js b/server/lib/db/blocks.js index d0c9a53..deb101f 100644 --- a/server/lib/db/blocks.js +++ b/server/lib/db/blocks.js @@ -21,6 +21,7 @@ function getBlocks(params, options, limit, cb) { if (limit < 1) { limit = 1; } + // Query mongo Block.find( params, @@ -59,7 +60,7 @@ function getBestHeight(cb) { if (err) { logger.log('error', `getBlock: ${err.err}`); - return cb(err); + return cb(err, null); } return cb(null, block.height); }); diff --git a/server/lib/db/transactions.js b/server/lib/db/transactions.js index 455727d..905d578 100644 --- a/server/lib/db/transactions.js +++ b/server/lib/db/transactions.js @@ -26,6 +26,7 @@ function getTransactions(params, options, limit, cb) { params, defaultOptions, (err, txs) => { + console.log(txs) if (err) { logger.log('error', `getTransactions: ${err}`); From 1218726ffc0363ef5c1b885e8c2972991e670207 Mon Sep 17 00:00:00 2001 From: Rob Riddle Date: Wed, 16 Aug 2017 16:31:57 -0400 Subject: [PATCH 35/44] This adds some basic regex validation on query parameters to harden against attacks and reduce time to error. Address validation could likely be improved beyond just regex, but this will do for now --- server/lib/api/transaction.js | 19 +++++++++++++++++++ server/lib/util/index.js | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index fb79b87..af5b7bd 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -2,6 +2,7 @@ const logger = require('../logger'); const request = require('request'); const config = require('../../config'); const db = require('../db'); +const util = require('../util'); const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; const MAX_TXS = config.api.max_txs; @@ -9,6 +10,12 @@ const MAX_TXS = config.api.max_txs; module.exports = function transactionAPI(router) { // Txs by txid router.get('/tx/:txid', (req, res) => { + if (!util.isTxid(req.params.txid)) { + return res.status(400).send({ + error: 'Invalid transaction id', + }); + } + // Get max block height for calculating confirmations db.blocks.getBestHeight( (err, blockHeight) => { @@ -75,6 +82,12 @@ module.exports = function transactionAPI(router) { 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', + }); + } + db.blocks.getBestHeight( (err, blockHeight) => { if (err) { @@ -128,6 +141,12 @@ module.exports = function transactionAPI(router) { }); }); } 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 db.blocks.getBestHeight( (err, blockHeight) => { diff --git a/server/lib/util/index.js b/server/lib/util/index.js index 5215dcf..ba0b444 100644 --- a/server/lib/util/index.js +++ b/server/lib/util/index.js @@ -21,8 +21,19 @@ function calcBlockReward(height) { 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 = { revHex, calcBlockReward, + isBlockHash: is64HexString, + isTxid: is64HexString, + isBitcoinAddress, }; From 1cb0a6b3853c8e6fcfca54b3837e456417bf96f0 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 21:23:29 -0400 Subject: [PATCH 36/44] indexes and experimenting with mem db --- server/config/index.js | 2 +- server/lib/api/address.js | 2 ++ server/lib/api/transaction.js | 2 +- server/models/input.js | 2 ++ server/models/output.js | 2 ++ server/models/transaction.js | 2 ++ 6 files changed, 10 insertions(+), 2 deletions(-) diff --git a/server/config/index.js b/server/config/index.js index 4c45558..827006d 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -4,7 +4,7 @@ const config = { bcoin_http: 'localhost', bcoin: { network: 'main', - db: 'leveldb', + db: 'mem', prefix: '.', checkpoints: true, workers: false, diff --git a/server/lib/api/address.js b/server/lib/api/address.js index 6359400..722adb2 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -8,6 +8,8 @@ const TTL = config.api.request_ttl; module.exports = function AddressAPI(router) { router.get('/addr/:addr', (req, res) => { const addr = req.params.addr || ''; + 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 }, diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 6948402..d07a398 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -147,7 +147,7 @@ module.exports = function transactionAPI(router) { const addr = req.query.address || ''; logger.log('debug', - 'Warning: Requesting data from Bcoin, may take some time'); + 'Warning: Requesting data from Bcoin by address, may take some time'); return request(`${API_URL}/tx/address/${addr}`, { timeout: TTL }, diff --git a/server/models/input.js b/server/models/input.js index 9f2c067..a6ebc1b 100644 --- a/server/models/input.js +++ b/server/models/input.js @@ -10,6 +10,8 @@ const InputSchema = new Schema({ address: { type: String, default: '' }, }); +InputSchema.index({ address: 1 }); + const Input = mongoose.model('Input', InputSchema); module.exports = Input; diff --git a/server/models/output.js b/server/models/output.js index 65f2194..56694eb 100644 --- a/server/models/output.js +++ b/server/models/output.js @@ -9,6 +9,8 @@ const OutputSchema = new Schema({ type: { type: String, default: '' }, }); +OutputSchema.index({ address: 1 }); + const Output = mongoose.model('Output', OutputSchema); module.exports = Output; diff --git a/server/models/transaction.js b/server/models/transaction.js index 0c5974b..8d30aba 100644 --- a/server/models/transaction.js +++ b/server/models/transaction.js @@ -22,6 +22,8 @@ const TransactionSchema = new Schema({ network: { type: String, default: '' }, }); +TransactionSchema.index({ hash: 1 }); + const Transaction = mongoose.model('Transaction', TransactionSchema); module.exports = Transaction; From 3e7f263e188cc12168ea506047e5fca20e225479 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Wed, 16 Aug 2017 21:56:56 -0400 Subject: [PATCH 37/44] bestBlockHeight set by node now. Less cb Hell. --- server/lib/api/transaction.js | 298 ++++++++++++++++------------------ server/lib/db/blocks.js | 25 ++- server/lib/db/transactions.js | 4 +- server/lib/node/index.js | 2 + 4 files changed, 162 insertions(+), 167 deletions(-) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index d07a398..c834902 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -11,60 +11,53 @@ module.exports = function transactionAPI(router) { // Txs by txid router.get('/tx/:txid', (req, res) => { // Get max block height for calculating confirmations - db.blocks.getBestHeight( - (err, blockHeight) => { - if (err) { - logger.log('err', err); + const height = db.blocks.bestHeight(); + // Bcoin transaction data + return request(`${API_URL}/tx/${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(); } - const height = blockHeight; - // Bcoin transaction data - return request(`${API_URL}/tx/${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 - 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, - })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', - }); - }); + // 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, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', + }); }); }); @@ -78,126 +71,111 @@ module.exports = function transactionAPI(router) { const rangeEnd = rangeStart + MAX_TXS; // get txs for blockhash, start with best height to calc confirmations if (req.query.block) { - db.blocks.getBestHeight( - (err, blockHeight) => { - if (err) { - logger.log('err', err); + + 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(); } - const height = blockHeight; - // 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); + 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, - })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', - })), - }); - }); + 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, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', + })), + }); }); } else if (req.query.address) { // Get txs by address, start with best height to calc confirmations - db.blocks.getBestHeight( - (err, blockHeight) => { - if (err) { - logger.log('err', err); + 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(); } - - const height = blockHeight; - 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, - })), - vout: tx.outputs.map(output => ({ - scriptPubKey: { - addresses: [output.address], - }, - value: output.value / 1e8, - })), - isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', - })), - }); - }); + // 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, + })), + vout: tx.outputs.map(output => ({ + scriptPubKey: { + addresses: [output.address], + }, + value: output.value / 1e8, + })), + isCoinBase: tx.inputs[0].prevout.hash === '0000000000000000000000000000000000000000000000000000000000000000', + })), + }); }); - } else { - // Get last n txs - return res.status(404).send({ error: 'Block hash or address expected' }); } + // Get last n txs + return res.status(404).send({ error: 'Block hash or address expected' }); }); router.get('/rawtx/:txid', (req, res) => { diff --git a/server/lib/db/blocks.js b/server/lib/db/blocks.js index deb101f..5659d34 100644 --- a/server/lib/db/blocks.js +++ b/server/lib/db/blocks.js @@ -4,6 +4,8 @@ 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 }; @@ -54,20 +56,31 @@ function getBlock(params, options, limit, cb) { return cb(null, blocks[0]); }); } -// Highest known height -function getBestHeight(cb) { +// Highest known height in mongo +function getBestHeight() { getBlock({}, {}, 1, (err, block) => { if (err) { logger.log('error', - `getBlock: ${err.err}`); - return cb(err, null); + `getBestHeight: ${err.err}`); + return; } - return cb(null, block.height); + 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, - getBestHeight, + bestHeight, }; diff --git a/server/lib/db/transactions.js b/server/lib/db/transactions.js index 905d578..bfd90c3 100644 --- a/server/lib/db/transactions.js +++ b/server/lib/db/transactions.js @@ -2,6 +2,9 @@ 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 + const MAX_TXS = config.api.max_txs; function getTransactions(params, options, limit, cb) { @@ -26,7 +29,6 @@ function getTransactions(params, options, limit, cb) { params, defaultOptions, (err, txs) => { - console.log(txs) if (err) { logger.log('error', `getTransactions: ${err}`); diff --git a/server/lib/node/index.js b/server/lib/node/index.js index 266f5b1..bea48c0 100644 --- a/server/lib/node/index.js +++ b/server/lib/node/index.js @@ -3,6 +3,7 @@ const logger = require('../../lib/logger'); const BlockParser = require('../parser').Block; const config = require('../../config'); const socket = require('../../lib/api/socket'); +const db = require('../../lib/db'); const node = new FullNode(config.bcoin); @@ -18,6 +19,7 @@ function start() { node.chain.on('connect', (entry, block) => { BlockParser.parse(entry, block); socket.processBlock(entry, block); + db.blocks.bestHeight(entry.height); }); node.on('error', (err) => { From 4b6482e0ade48173d526b8836954cbf46acf965b Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Thu, 17 Aug 2017 14:37:08 -0400 Subject: [PATCH 38/44] add scriptsigs to txs for circle-plus info on tx lists --- server/config/index.js | 2 +- server/lib/api/transaction.js | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/server/config/index.js b/server/config/index.js index 827006d..4c45558 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -4,7 +4,7 @@ const config = { bcoin_http: 'localhost', bcoin: { network: 'main', - db: 'mem', + db: 'leveldb', prefix: '.', checkpoints: true, workers: false, diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index c834902..752723b 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -35,6 +35,8 @@ module.exports = function transactionAPI(router) { return res.status(404).send(); } + console.log(tx); + // Return UI JSON return res.send({ txid: tx.hash, @@ -49,9 +51,13 @@ module.exports = function transactionAPI(router) { 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, @@ -71,7 +77,6 @@ module.exports = function transactionAPI(router) { const rangeEnd = rangeStart + MAX_TXS; // get txs for blockhash, start with best height to calc confirmations if (req.query.block) { - const height = db.blocks.bestHeight(); // Get Bcoin data return request(`${API_URL}/block/${req.query.block}`, @@ -110,9 +115,13 @@ module.exports = function transactionAPI(router) { 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, @@ -162,9 +171,13 @@ module.exports = function transactionAPI(router) { 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, From d148b5ec7710dab823c9da677f4c00bf0dd84cf0 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Thu, 17 Aug 2017 15:11:27 -0400 Subject: [PATCH 39/44] updated readme instructions --- README.md | 4 ++-- server/lib/api/transaction.js | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f4feeca..b733135 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ ## 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 To get started, clone this repository, then – with `mongod` running – install and run insight: ```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 start ``` diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 752723b..7afbc52 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -35,8 +35,6 @@ module.exports = function transactionAPI(router) { return res.status(404).send(); } - console.log(tx); - // Return UI JSON return res.send({ txid: tx.hash, From 898e373b77a7c0bb845c1359caaf7cad39fa8e64 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Thu, 17 Aug 2017 16:28:30 -0400 Subject: [PATCH 40/44] Add util to other routes for input validation. --- server/lib/api/address.js | 8 ++++++++ server/lib/api/block.js | 20 ++++++++++++++++++-- server/lib/api/message.js | 9 ++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/server/lib/api/address.js b/server/lib/api/address.js index 722adb2..719a8cc 100644 --- a/server/lib/api/address.js +++ b/server/lib/api/address.js @@ -1,6 +1,7 @@ const logger = require('../logger'); const request = require('request'); const config = require('../../config'); +const util = require('../util'); const API_URL = `http://${config.bcoin_http}:${config.bcoin['http-port']}`; const TTL = config.api.request_ttl; @@ -8,6 +9,13 @@ const TTL = config.api.request_ttl; module.exports = function AddressAPI(router) { router.get('/addr/:addr', (req, res) => { 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 diff --git a/server/lib/api/block.js b/server/lib/api/block.js index 482ea0e..ad14eb5 100644 --- a/server/lib/api/block.js +++ b/server/lib/api/block.js @@ -1,11 +1,20 @@ const logger = require('../logger'); -const db = require('../db'); +const db = require('../db'); +const util = require('../util'); module.exports = function BlockAPI(router) { router.get('/block/:blockHash', (req, res) => { + const blockHash = 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: req.params.blockHash }, + { hash: blockHash }, { rawBlock: 0 }, 1, (err, block) => { @@ -73,6 +82,13 @@ module.exports = function BlockAPI(router) { router.get('/rawblock/:blockHash', (req, res) => { const blockHash = 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 }, diff --git a/server/lib/api/message.js b/server/lib/api/message.js index 42ba63e..27c0703 100644 --- a/server/lib/api/message.js +++ b/server/lib/api/message.js @@ -1,10 +1,17 @@ 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) { res.json({ message: 'Missing parameters (expected "address", "signature" and "message")', From d2f68fefe9e9e568c8abf693a9ef20c87ebaaebc Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Thu, 17 Aug 2017 17:41:07 -0400 Subject: [PATCH 41/44] need new mongo models from another branch --- server/config/index.js | 3 ++- server/lib/api/transaction.js | 14 ++++++++++++-- server/lib/db/transactions.js | 32 +++++++++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/server/config/index.js b/server/config/index.js index 4c45558..50638e3 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -28,7 +28,8 @@ const config = { ticker_url: 'https://www.bitstamp.net/api/ticker/', ticker_prop: 'bitstamp', max_blocks: 72, - max_txs: 10, + max_txs: 50, + max_page_txs: 10, request_ttl: 100000, }, }; diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index d0f742b..15c79d1 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -86,7 +86,7 @@ module.exports = function transactionAPI(router) { 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}`, @@ -204,7 +204,17 @@ module.exports = function transactionAPI(router) { }); } // Get last n txs - return res.status(404).send({ error: 'Block hash or address expected' }); + console.log('GETTING 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); + }); + + // return res.status(404).send({ error: 'Block hash or address expected' }); }); router.get('/rawtx/:txid', (req, res) => { diff --git a/server/lib/db/transactions.js b/server/lib/db/transactions.js index bfd90c3..68d93df 100644 --- a/server/lib/db/transactions.js +++ b/server/lib/db/transactions.js @@ -4,9 +4,12 @@ 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_TXS = config.api.max_page_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 }; @@ -17,8 +20,8 @@ function getTransactions(params, options, limit, cb) { limit = 1; } - if (limit > MAX_TXS) { - limit = MAX_TXS; + if (limit > MAX_PAGE_TXS) { + limit = MAX_PAGE_TXS; } if (limit < 1) { @@ -57,7 +60,30 @@ function getTransaction(params, options, limit, cb) { }); } +// 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, }; From 9b7336b066754c8604f220c9015ef12f649593c1 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Thu, 17 Aug 2017 17:44:47 -0400 Subject: [PATCH 42/44] New models and parser up --- server/lib/parser/block.js | 71 ++++++++------------------------ server/lib/parser/index.js | 2 + server/lib/parser/transaction.js | 67 ++++++++++++++++++++++++++++++ server/models/block.js | 32 +++++++------- server/models/transaction.js | 34 +++++++-------- 5 files changed, 120 insertions(+), 86 deletions(-) create mode 100644 server/lib/parser/transaction.js diff --git a/server/lib/parser/block.js b/server/lib/parser/block.js index b19b2b9..fb0000d 100644 --- a/server/lib/parser/block.js +++ b/server/lib/parser/block.js @@ -1,67 +1,32 @@ const BlockModel = require('../../models/block'); -const InputModel = require('../../models/input'); -const OutputModel = require('../../models/output'); -const config = require('../../config'); -const util = require('../../lib/util'); -const logger = require('../logger'); +const config = require('../../config'); +const util = require('../../lib/util'); +const logger = require('../logger'); function parse(entry, block) { - const rawBlock = block.toRaw().toString('hex'); + const rawBlock = block.toRaw().toString('hex'); const blockJSON = block.toJSON(); - const reward = util.calcBlockReward(entry.height); + const reward = util.calcBlockReward(entry.height); // Can probably use destructuring to build something nicer const newBlock = new BlockModel({ - hash: blockJSON.hash, - height: entry.height, - size: block.getSize(), - version: blockJSON.version, - prevBlock: blockJSON.prevBlock, + hash: blockJSON.hash, + height: entry.height, + size: block.getSize(), + version: blockJSON.version, + prevBlock: blockJSON.prevBlock, merkleRoot: blockJSON.merkleRoot, - ts: blockJSON.ts, - bits: blockJSON.bits, - nonce: blockJSON.nonce, - txs: block.txs.map((tx) => { + ts: blockJSON.ts, + bits: blockJSON.bits, + nonce: blockJSON.nonce, + txs: block.txs.map((tx) => { const txJSON = tx.toJSON(); - return { - 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, - }; + return txJSON.hash; }), - chainwork: entry.chainwork, + chainwork: entry.chainwork, reward, - network: config.bcoin.network, - poolInfo: {}, + network: config.bcoin.network, + poolInfo: {}, rawBlock, }); diff --git a/server/lib/parser/index.js b/server/lib/parser/index.js index 13c2d5b..5bd9726 100644 --- a/server/lib/parser/index.js +++ b/server/lib/parser/index.js @@ -1,5 +1,7 @@ const Block = require('./block'); +const Transaction = require('./transaction'); module.exports = { Block, + Transaction, }; diff --git a/server/lib/parser/transaction.js b/server/lib/parser/transaction.js new file mode 100644 index 0000000..7b0a8ab --- /dev/null +++ b/server/lib/parser/transaction.js @@ -0,0 +1,67 @@ +const TxModel = require('../../models/transaction'); +const InputModel = require('../../models/input'); +const OutputModel = require('../../models/output'); +const config = require('../../config'); +const util = require('../../lib/util'); +const logger = require('../logger'); +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) { + txs.forEach((tx) => { + const txJSON = tx.toJSON(); + const txRAW = tx.toRaw(); + + const t = new TxModel({ + hash: txJSON.hash, + witnessHash: txJSON.witnessHash, + fee: txJSON.fee, + rate: txJSON.rate, + size: txRAW.length, + 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, + }); + + + t.save((err) => { + if (err) { + logger.log('error', err.message); + } + }); + }); +} + +module.exports = { + parse, +}; diff --git a/server/models/block.js b/server/models/block.js index 38ba3aa..84aed55 100644 --- a/server/models/block.js +++ b/server/models/block.js @@ -1,24 +1,24 @@ -const mongoose = require('mongoose'); +const mongoose = require('mongoose'); const Transaction = require('./transaction'); const Schema = mongoose.Schema; const BlockSchema = new Schema({ - hash: { type: String, default: '' }, - height: { type: Number, default: 0 }, - size: { type: Number, default: 0 }, - version: { type: Number, default: 0 }, - prevBlock: { type: String, default: '' }, - merkleRoot: { type: String, default: '' }, - ts: { type: Number, default: 0 }, - bits: { type: Number, default: 0 }, - nonce: { type: Number, default: 0 }, - txs: [Transaction.schema], - chainwork: { type: Number, default: 0 }, - reward: { type: Number, default: 0 }, - network: { type: String, default: '' }, - poolInfo: { type: Object, default: {} }, - rawBlock: { type: String, default: '' }, + hash: { type: String, default: '' }, + height: { type: Number, default: 0 }, + size: { type: Number, default: 0 }, + version: { type: Number, default: 0 }, + prevBlock: { type: String, default: '' }, + merkleRoot: { type: String, default: '' }, + ts: { type: Number, default: 0 }, + bits: { type: Number, default: 0 }, + nonce: { type: Number, default: 0 }, + txs: [{ type: String, default: '' }], + chainwork: { type: Number, default: 0 }, + reward: { type: Number, default: 0 }, + network: { type: String, default: '' }, + poolInfo: { type: Object, default: {} }, + rawBlock: { type: String, default: '' }, }, { toJSON: { virtuals: true, diff --git a/server/models/transaction.js b/server/models/transaction.js index 8d30aba..e4e3416 100644 --- a/server/models/transaction.js +++ b/server/models/transaction.js @@ -1,25 +1,25 @@ const mongoose = require('mongoose'); -const Input = require('./input'); -const Output = require('./output'); +const Input = require('./input'); +const Output = require('./output'); -const Schema = mongoose.Schema; +const Schema = mongoose.Schema; const TransactionSchema = new Schema({ - hash: { type: String, default: '' }, + hash: { type: String, default: '' }, witnessHash: { type: String, default: '' }, - fee: { type: Number, default: 0 }, - rate: { type: Number, default: 0 }, - ps: { type: Number, default: 0 }, - height: { type: Number, default: 0 }, - block: { type: String, default: '' }, - index: { type: Number, default: 0 }, - version: { type: Number, default: 0 }, - flag: { type: Number, default: 0 }, - lockTime: { type: Number, default: 0 }, - inputs: [Input.schema], - outputs: [Output.schema], - size: { type: Number, default: 0 }, - network: { type: String, default: '' }, + fee: { type: Number, default: 0 }, + rate: { type: Number, default: 0 }, + ps: { type: Number, default: 0 }, + height: { type: Number, default: 0 }, + block: { type: String, default: '' }, + index: { type: Number, default: 0 }, + version: { type: Number, default: 0 }, + flag: { type: Number, default: 0 }, + lockTime: { type: Number, default: 0 }, + inputs: [Input.schema], + outputs: [Output.schema], + size: { type: Number, default: 0 }, + network: { type: String, default: '' }, }); TransactionSchema.index({ hash: 1 }); From 805e5ec0c35a6f8e1c04ff6150f990b1d27f40d1 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Thu, 17 Aug 2017 17:49:54 -0400 Subject: [PATCH 43/44] top n txs setup --- server/lib/api/transaction.js | 4 +--- server/lib/db/transactions.js | 2 +- server/lib/node/index.js | 2 ++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index 15c79d1..da19698 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -204,17 +204,15 @@ module.exports = function transactionAPI(router) { }); } // Get last n txs - console.log('GETTING N TXS'); db.txs.getTopTransactions((err, txs) => { if (err) { logger.log('err', `/txs getTopTransactions ${err}`); return res.status(404).send(err); } + console.log(txs.length); return res.json(txs); }); - - // return res.status(404).send({ error: 'Block hash or address expected' }); }); router.get('/rawtx/:txid', (req, res) => { diff --git a/server/lib/db/transactions.js b/server/lib/db/transactions.js index 68d93df..151e5d6 100644 --- a/server/lib/db/transactions.js +++ b/server/lib/db/transactions.js @@ -6,7 +6,7 @@ const config = require('../../config'); // These will be replaced with more advanced mongo // No optimization yet. -const MAX_TXS = config.api.max_page_txs; +const MAX_TXS = config.api.max_txs; const MAX_PAGE_TXS = config.api.max_page_txs; // For Paging diff --git a/server/lib/node/index.js b/server/lib/node/index.js index bea48c0..9a99379 100644 --- a/server/lib/node/index.js +++ b/server/lib/node/index.js @@ -1,6 +1,7 @@ const FullNode = require('bcoin/lib/node/fullnode'); const logger = require('../../lib/logger'); const BlockParser = require('../parser').Block; +const TxParser = require('../parser').Transaction; const config = require('../../config'); const socket = require('../../lib/api/socket'); const db = require('../../lib/db'); @@ -18,6 +19,7 @@ function start() { node.chain.on('connect', (entry, block) => { BlockParser.parse(entry, block); + TxParser.parse(entry, block.txs); socket.processBlock(entry, block); db.blocks.bestHeight(entry.height); }); From 8231ed6a1e456775332ddc77cb742eda93ae50a8 Mon Sep 17 00:00:00 2001 From: tenthirtyone Date: Fri, 18 Aug 2017 14:17:15 -0400 Subject: [PATCH 44/44] clean up console log and fix return statements --- server/lib/api/message.js | 6 ++---- server/lib/api/transaction.js | 4 +--- server/lib/db/blocks.js | 3 +-- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/server/lib/api/message.js b/server/lib/api/message.js index 27c0703..a15802a 100644 --- a/server/lib/api/message.js +++ b/server/lib/api/message.js @@ -13,21 +13,19 @@ function verifyMessage(req, res) { } if (!address || !signature || !message) { - res.json({ + return res.json({ message: 'Missing parameters (expected "address", "signature" and "message")', code: 1, }); - return; } let valid; try { valid = new Message(message).verify(address, signature); } catch (err) { - res.json({ + return res.json({ message: `Unexpected error: ${err.message}`, code: 1, }); - return; } res.json({ result: valid }); } diff --git a/server/lib/api/transaction.js b/server/lib/api/transaction.js index da19698..5a93f14 100644 --- a/server/lib/api/transaction.js +++ b/server/lib/api/transaction.js @@ -210,7 +210,6 @@ module.exports = function transactionAPI(router) { `/txs getTopTransactions ${err}`); return res.status(404).send(err); } - console.log(txs.length); return res.json(txs); }); }); @@ -229,8 +228,7 @@ module.exports = function transactionAPI(router) { if (err) { logger.log('error', `${err}`); - res.status(400).send(err); - return; + return res.status(400).send(err); } res.json(true); diff --git a/server/lib/db/blocks.js b/server/lib/db/blocks.js index 5659d34..2b3bb75 100644 --- a/server/lib/db/blocks.js +++ b/server/lib/db/blocks.js @@ -60,9 +60,8 @@ function getBlock(params, options, limit, cb) { function getBestHeight() { getBlock({}, {}, 1, (err, block) => { if (err) { - logger.log('error', + return logger.log('error', `getBestHeight: ${err.err}`); - return; } bestBlockHeight = block.height; });