Browse Source

Lots more stuff:

- /mempool-summary - switch to be a dynamic page with loading progress bar like /mining-summary and /block-stats
- more options for block ranges on /mining-summary and /block-stats
- "unsupported" alert on /block-stats for node versions below 0.17.0
- frontend improvements for dynamic pages to better display currency values (matching non-dynamic pages)
- re-order tools lists
- miner config for block #9 (satoshi)...for fun
- clean up spacing on homepage
- fix bug where miner info wasn't showing on tx pages
master
Dan Janosik 5 years ago
parent
commit
bafb9bfb91
No known key found for this signature in database GPG Key ID: C6F8CE9FFDB2CED2
  1. 1
      CHANGELOG.md
  2. 6
      app.js
  3. 12
      app/api/coreApi.js
  4. 52
      app/api/rpcApi.js
  5. 2
      app/config.js
  6. 9
      app/utils.js
  7. 7
      public/txt/mining-pools-configs/BTC/2.json
  8. 52
      routes/apiRouter.js
  9. 34
      routes/baseActionsRouter.js
  10. 38
      routes/snippetRouter.js
  11. 201
      views/block-stats.pug
  12. 3
      views/includes/debug-overrides.pug
  13. 4
      views/includes/index-network-summary.pug
  14. 4
      views/includes/tools-card.pug
  15. 10
      views/index.pug
  16. 730
      views/mempool-summary.pug
  17. 224
      views/mining-summary.pug
  18. 14
      views/transaction.pug

1
CHANGELOG.md

@ -28,6 +28,7 @@
* Min / Max tx sizes
* New tool `/block-stats` for viewing summarized block data from recent blocks
* New tool `/mining-summary` for viewing summarized mining data from recent blocks
* Change `/mempool-summary` to load data via ajax (UX improvement to give feedback while loading large data sets)
* Zero-indexing for tx inputs/outputs (#173)
* Labels for transaction output types
* Configurable UI "sub-header" links

6
app.js

@ -51,8 +51,9 @@ global.appVersion = package_json.version;
var crawlerBotUserAgentStrings = [ "Googlebot", "Bingbot", "Slurp", "DuckDuckBot", "Baiduspider", "YandexBot", "Sogou", "Exabot", "facebot", "ia_archiver" ];
var baseActionsRouter = require('./routes/baseActionsRouter');
var apiActionsRouter = require('./routes/api');
var baseActionsRouter = require('./routes/baseActionsRouter.js');
var apiActionsRouter = require('./routes/apiRouter.js');
var snippetActionsRouter = require('./routes/snippetRouter.js');
var app = express();
@ -584,6 +585,7 @@ app.use(csurf(), (req, res, next) => {
app.use('/', baseActionsRouter);
app.use('/api/', apiActionsRouter);
app.use('/snippet/', snippetActionsRouter);
app.use(function(req, res, next) {
var time = Date.now() - req.startTime;

12
app/api/coreApi.js

@ -159,6 +159,11 @@ function getMempoolInfo() {
return tryCacheThenRpcApi(miscCache, "getMempoolInfo", 1000, rpcApi.getMempoolInfo);
}
function getMempoolTxids() {
// no caching, that would be dumb
return rpcApi.getMempoolTxids();
}
function getMiningInfo() {
return tryCacheThenRpcApi(miscCache, "getMiningInfo", 30 * 1000, rpcApi.getMiningInfo);
}
@ -722,9 +727,9 @@ function getUtxo(txid, outputIndex) {
});
}
function getMempoolTxDetails(txid) {
return tryCacheThenRpcApi(miscCache, "mempoolTxDetails-" + txid, 3600000, function() {
return rpcApi.getMempoolTxDetails(txid);
function getMempoolTxDetails(txid, includeAncDec) {
return tryCacheThenRpcApi(miscCache, "mempoolTxDetails-" + txid + "-" + includeAncDec, 3600000, function() {
return rpcApi.getMempoolTxDetails(txid, includeAncDec);
});
}
@ -996,6 +1001,7 @@ module.exports = {
getNetworkInfo: getNetworkInfo,
getNetTotals: getNetTotals,
getMempoolInfo: getMempoolInfo,
getMempoolTxids: getMempoolTxids,
getMiningInfo: getMiningInfo,
getBlockByHeight: getBlockByHeight,
getBlocksByHeight: getBlocksByHeight,

52
app/api/rpcApi.js

@ -24,6 +24,8 @@ var rpcQueue = async.queue(function(task, callback) {
}, config.rpcConcurrency);
var minRpcVersions = {getblockstats:"0.17.0"};
function getBlockchainInfo() {
@ -67,7 +69,7 @@ function getNetworkHashrate(blockCount=144) {
}
function getBlockStats(hash) {
if (semver.gte(global.btcNodeSemver, "0.17.0")) {
if (semver.gte(global.btcNodeSemver, minRpcVersions.getblockstats)) {
if (hash == coinConfig.genesisBlockHashesByNetwork[global.activeBlockchain] && coinConfig.genesisBlockStatsByNetwork[global.activeBlockchain]) {
return new Promise(function(resolve, reject) {
resolve(coinConfig.genesisBlockStatsByNetwork[global.activeBlockchain]);
@ -78,12 +80,12 @@ function getBlockStats(hash) {
}
} else {
// unsupported
return nullPromise();
return unsupportedPromise(minRpcVersions.getblockstats);
}
}
function getBlockStatsByHeight(height) {
if (semver.gte(global.btcNodeSemver, "0.17.0")) {
if (semver.gte(global.btcNodeSemver, minRpcVersions.getblockstats)) {
if (height == 0 && coinConfig.genesisBlockStatsByNetwork[global.activeBlockchain]) {
return new Promise(function(resolve, reject) {
resolve(coinConfig.genesisBlockStatsByNetwork[global.activeBlockchain]);
@ -94,7 +96,7 @@ function getBlockStatsByHeight(height) {
}
} else {
// unsupported
return nullPromise();
return unsupportedPromise(minRpcVersions.getblockstats);
}
}
@ -256,7 +258,7 @@ function getUtxo(txid, outputIndex) {
});
}
function getMempoolTxDetails(txid) {
function getMempoolTxDetails(txid, includeAncDec=true) {
debugLog("getMempoolTxDetails: %s", txid);
var promises = [];
@ -274,27 +276,29 @@ function getMempoolTxDetails(txid) {
});
}));
promises.push(new Promise(function(resolve, reject) {
getRpcDataWithParams({method:"getmempoolancestors", parameters:[txid]}).then(function(result) {
mempoolDetails.ancestors = result;
if (includeAncDec) {
promises.push(new Promise(function(resolve, reject) {
getRpcDataWithParams({method:"getmempoolancestors", parameters:[txid]}).then(function(result) {
mempoolDetails.ancestors = result;
resolve();
resolve();
}).catch(function(err) {
reject(err);
});
}));
}).catch(function(err) {
reject(err);
});
}));
promises.push(new Promise(function(resolve, reject) {
getRpcDataWithParams({method:"getmempooldescendants", parameters:[txid]}).then(function(result) {
mempoolDetails.descendants = result;
promises.push(new Promise(function(resolve, reject) {
getRpcDataWithParams({method:"getmempooldescendants", parameters:[txid]}).then(function(result) {
mempoolDetails.descendants = result;
resolve();
resolve();
}).catch(function(err) {
reject(err);
});
}));
}).catch(function(err) {
reject(err);
});
}));
}
return new Promise(function(resolve, reject) {
Promise.all(promises).then(function() {
@ -370,9 +374,9 @@ function getRpcDataWithParams(request) {
});
}
function nullPromise() {
function unsupportedPromise(minRpcVersionNeeded) {
return new Promise(function(resolve, reject) {
resolve(null);
resolve({success:false, error:"Unsupported", minRpcVersionNeeded:minRpcVersionNeeded});
});
}
@ -401,4 +405,6 @@ module.exports = {
getNetworkHashrate: getNetworkHashrate,
getBlockStats: getBlockStats,
getBlockStatsByHeight: getBlockStatsByHeight,
minRpcVersions: minRpcVersions
};

2
app/config.js

@ -165,7 +165,7 @@ module.exports = {
}
]
},
subHeaderToolsList:[0, 1, 4, 9, 10, 6, 7], // indexes in "siteTools" below that are shown in the site "sub menu" (visible on all pages except homepage)
subHeaderToolsList:[0, 10, 9, 4, 1, 6, 7], // indexes in "siteTools" below that are shown in the site "sub menu" (visible on all pages except homepage)
toolsDropdownIndexList: [0, 1, 4, 10, 9, 3, 2, 5, 6, 7, 8],
},

9
app/utils.js

@ -382,6 +382,15 @@ function getMinerFromCoinbaseTx(tx) {
}
}
}
for (var blockHash in miningPoolsConfig.block_hashes) {
if (blockHash == tx.blockhash) {
var minerInfo = miningPoolsConfig.block_hashes[blockHash];
minerInfo.identifiedBy = "known block hash '" + blockHash + "'";
return minerInfo;
}
}
}
}

7
public/txt/mining-pools-configs/BTC/2.json

@ -4,5 +4,12 @@
"name" : "Satoshi",
"link" : "https://bitcoin.org"
}
},
"block_hashes" : {
"000000008d9dc510f23c2657fc4f67bea30078cc05a90eb89e84cc475c080805": {
"name" : "Satoshi",
"link" : "https://bitcoin.org",
"note" : "It's known that Satoshi sent BTC to Hal in #170 and this block's coinbase is the output spent there."
}
}
}

52
routes/api.js → routes/apiRouter.js

@ -56,6 +56,58 @@ router.get("/block-stats-by-height/:blockHeights", function(req, res, next) {
});
});
router.get("/mempool-txs/:txids", function(req, res, next) {
var txids = req.params.txids.split(",");
var promises = [];
for (var i = 0; i < txids.length; i++) {
promises.push(coreApi.getMempoolTxDetails(txids[i], false));
}
Promise.all(promises).then(function(results) {
res.json(results);
next();
}).catch(function(err) {
res.json({success:false, error:err});
next();
});
});
router.get("/utils/:func/:params", function(req, res, next) {
var func = req.params.func;
var params = req.params.params;
var data = null;
if (func == "formatLargeNumber") {
if (params.indexOf(",") > -1) {
var parts = params.split(",");
data = utils.formatLargeNumber(parseInt(parts[0]), parseInt(parts[1]));
} else {
data = utils.formatLargeNumber(parseInt(params));
}
} else if (func == "formatCurrencyAmountInSmallestUnits") {
var parts = params.split(",");
data = utils.formatCurrencyAmountInSmallestUnits(new Decimal(parts[0]), parseInt(parts[1]));
console.log("ABC: " + JSON.stringify(data));
} else {
data = {success:false, error:`Unknown function: ${func}`};
}
res.json(data);
next();
});
module.exports = router;

34
routes/baseActionsRouter.js

@ -13,12 +13,14 @@ var sha256 = require("crypto-js/sha256");
var hexEnc = require("crypto-js/enc-hex");
var Decimal = require("decimal.js");
var marked = require("marked");
var semver = require("semver");
var utils = require('./../app/utils.js');
var coins = require("./../app/coins.js");
var config = require("./../app/config.js");
var coreApi = require("./../app/api/coreApi.js");
var addressApi = require("./../app/api/addressApi.js");
var rpcApi = require("./../app/api/rpcApi.js");
const forceCsrf = csurf({ ignoreMethods: [] });
@ -220,16 +222,28 @@ router.get("/node-status", function(req, res, next) {
});
router.get("/mempool-summary", function(req, res, next) {
coreApi.getMempoolInfo().then(function(getmempoolinfo) {
res.locals.getmempoolinfo = getmempoolinfo;
res.locals.satoshiPerByteBucketMaxima = coinConfig.feeSatoshiPerByteBucketMaxima;
coreApi.getMempoolStats().then(function(mempoolstats) {
res.locals.mempoolstats = mempoolstats;
coreApi.getMempoolTxids().then(function(mempooltxids) {
var debugMaxCount = 0;
res.render("mempool-summary");
if (debugMaxCount > 0) {
var debugtxids = [];
for (var i = 0; i < Math.min(10000, mempooltxids.length); i++) {
debugtxids.push(mempooltxids[i]);
}
res.locals.mempooltxidChunks = utils.splitArrayIntoChunks(debugtxids, 25);
} else {
res.locals.mempooltxidChunks = utils.splitArrayIntoChunks(mempooltxids, 25);
}
res.render("mempool-summary");
next();
next();
});
}).catch(function(err) {
res.locals.userMessage = "Error: " + err;
@ -437,6 +451,10 @@ router.get("/mining-summary", function(req, res, next) {
});
router.get("/block-stats", function(req, res, next) {
if (semver.lt(global.btcNodeSemver, rpcApi.minRpcVersions.getblockstats)) {
res.locals.rpcApiUnsupportedError = {rpc:"getblockstats", version:rpcApi.minRpcVersions.getblockstats};
}
coreApi.getBlockchainInfo().then(function(getblockchaininfo) {
res.locals.currentBlockHeight = getblockchaininfo.blocks;
@ -741,7 +759,7 @@ router.get("/tx/:transactionId", function(req, res, next) {
if (rawTxResult.confirmations == null) {
promises.push(new Promise(function(resolve, reject) {
coreApi.getMempoolTxDetails(txid).then(function(mempoolDetails) {
coreApi.getMempoolTxDetails(txid, true).then(function(mempoolDetails) {
res.locals.mempoolDetails = mempoolDetails;
resolve();

38
routes/snippetRouter.js

@ -0,0 +1,38 @@
var debug = require("debug");
var debugLog = debug("btcexp:router");
var express = require('express');
var csurf = require('csurf');
var router = express.Router();
var util = require('util');
var moment = require('moment');
var bitcoinCore = require("bitcoin-core");
var qrcode = require('qrcode');
var bitcoinjs = require('bitcoinjs-lib');
var sha256 = require("crypto-js/sha256");
var hexEnc = require("crypto-js/enc-hex");
var Decimal = require("decimal.js");
var marked = require("marked");
var utils = require('./../app/utils.js');
var coins = require("./../app/coins.js");
var config = require("./../app/config.js");
var coreApi = require("./../app/api/coreApi.js");
var addressApi = require("./../app/api/addressApi.js");
const forceCsrf = csurf({ ignoreMethods: [] });
router.get("/formatCurrencyAmount/:amt", function(req, res, next) {
res.locals.currencyValue = req.params.amt;
res.render("includes/value-display");
next();
});
module.exports = router;

201
views/block-stats.pug

@ -7,41 +7,111 @@ block content
h1.h3 Block Stats
hr
div.mb-4(id="time-range-buttons")
h3.h5 Block Count
div.btn-group
a.btn.btn-primary.block-count-btn(href="javascript:void(0)", data-blockCount="144") 144
small.ml-2 (~1d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="432") 432
small.ml-2 (~3d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="1008") 1,008
small.ml-2 (~7d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="4320") 4,320
small.ml-2 (~30d)
div#progress-wrapper
h4.h6 Loading blocks:
span(id="block-progress-text")
div.progress(id="progress-bar", style="height: 7px; margin-bottom: 200px;")
div.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
if (rpcApiUnsupportedError)
div.alert.alert-danger(style="margin-bottom: 200px;")
span Your node version doesn't support the
span.text-monospace #{rpcApiUnsupportedError.rpc}
span RPC. To use this tool you need to upgrade to
span.text-monospace v#{rpcApiUnsupportedError.version}
span or later.
else
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Selected Blocks
hr
div.clearfix
div.float-left.mr-4
div(id="time-range-buttons")
h3.h6 Latest N Blocks
div.btn-group
a.btn.btn-primary.block-count-btn(href="javascript:void(0)", data-blockCount="144") 144
small.ml-2 (~1d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="432") 432
small.ml-2 (~3d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="1008") 1,008
small.ml-2 (~7d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="4320") 4,320
small.ml-2 (~30d)
div.float-left.mr-4
div(id="block-selections-buttons")
h3.h6 Preconfigured
div.dropdown
button.btn.btn-outline-primary.dropdown-toggle(type="button", id="preconfigured-dropdown", data-toggle="dropdown", aria-haspopup="true", aria-expanded="false") Selections
div.dropdown-menu(aria-labelledby="preconfigured-dropdown")
a.dropdown-item(href="javascript:void(0)", data-blocks="0-199") First 200 Blocks
a.dropdown-item(href="javascript:void(0)", data-blocks="209900-210100") First Halving
a.dropdown-item(href="javascript:void(0)", data-blocks="419900-420100") Second Halving
a.dropdown-item(href="javascript:void(0)", data-blocks="481724-481924") SegWit Activation Window
div.float-left
div(id="time-range-buttons")
h3.h6 Custom Range
form.form-inline.mr-3(id="custom-range-form")
div.input-group
input.form-control(type="text", id="custom-range-start", placeholder="min height", style="width: 125px;")
input.form-control(type="text", id="custom-range-end", placeholder="max height", style="width: 125px;")
div.input-group-append
button.btn.btn-primary(type="submit") Go
div#progress-wrapper
div.card.shadow-sm.mb-3
div.card-body
h4.h6 Loading blocks:
span(id="block-progress-text")
div.progress(id="progress-bar", style="height: 7px;")
div.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
div(id="main-content", style="display: none;")
canvas.mb-3(id="fee-rates")
div.card.shadow-sm.mb-3
div.card-body
canvas.mb-3(id="fee-rates", height="100")
canvas.mb-3(id="max-fee-rates")
div.card.shadow-sm.mb-3
div.card-body
canvas.mb-3(id="max-fee-rates", height="100")
canvas.mb-3(id="min-fees")
canvas.mb-3(id="max-fees")
div.card.shadow-sm.mb-3
div.card-body
canvas.mb-3(id="min-fees", height="100")
div.card.shadow-sm.mb-3
div.card-body
canvas.mb-3(id="max-fees", height="100")
div.card.shadow-sm.mb-3
div.card-body
canvas.mb-3(id="inputs-outputs", height="100")
div.card.shadow-sm.mb-3
div.card-body
canvas.mb-3(id="tx-sizes", height="100")
div.card.shadow-sm.mb-3
div.card-body
canvas.mb-3(id="max-tx-sizes", height="100")
canvas.mb-3(id="inputs-outputs")
div.card.shadow-sm.mb-3
div.card-body
canvas.mb-3(id="volumes", height="100")
canvas.mb-3(id="tx-sizes")
canvas.mb-3(id="max-tx-sizes")
div.card.shadow-sm.mb-3
div.card-body
canvas.mb-3(id="weights-sizes", height="100")
canvas.mb-3(id="volumes")
div.card.shadow-sm.mb-3
div.card-body
canvas.mb-3(id="sw-txs", height="100")
canvas.mb-3(id="weights-sizes")
div.card.shadow-sm.mb-3
div.card-body
canvas.mb-3(id="subsidy", height="100")
@ -58,10 +128,9 @@ block endOfBody
// highlight current selection
$("#time-range-buttons .block-count-btn").removeClass("btn-primary").addClass("btn-outline-primary");
$(this).addClass("btn-primary").removeClass("btn-outline-primary");
$("#preconfigured-dropdown").removeClass("btn-primary").addClass("btn-outline-primary");
var blockCount = parseInt($(this).attr("data-blockCount"));
$(".miner-summary-row").remove();
$("#data-progress").css("width", "0%");
$("#block-progress-text").text("");
@ -72,17 +141,56 @@ block endOfBody
getData(currentBlockHeight, blockCount, 15);
});
$("#block-selections-buttons .dropdown-item").on("click", function() {
// highlight current selection
$("#time-range-buttons .block-count-btn").removeClass("btn-primary").addClass("btn-outline-primary");
$("#preconfigured-dropdown").removeClass("btn-outline-primary").addClass("btn-primary");
var blocks = $(this).attr("data-blocks");
var bStartEnd = blocks.split("-");
var bStart = parseInt(bStartEnd[0]);
var bEnd = parseInt(bStartEnd[1]);
$("#data-progress").css("width", "0%");
$("#block-progress-text").text("");
$("#main-content").hide();
$("#progress-wrapper").show();
getData(bEnd, bEnd - bStart + 1, 15);
});
$("#custom-range-form").on("submit", function() {
// highlight current selection
$("#time-range-buttons .block-count-btn").removeClass("btn-primary").addClass("btn-outline-primary");
$("#preconfigured-dropdown").removeClass("btn-primary").addClass("btn-outline-primary");
var bStart = parseInt($("#custom-range-start").val());
var bEnd = parseInt($("#custom-range-end").val());
$("#data-progress").css("width", "0%");
$("#block-progress-text").text("");
$("#main-content").hide();
$("#progress-wrapper").show();
getData(bEnd, bEnd - bStart + 1, 15);
return false;
});
getData(currentBlockHeight, 144, 15);
});
function getData(blockStart, count, chunkSize) {
$("#time-range-buttons .block-count-btn").addClass("disabled");
$("#block-selections-buttons .dropdown-item").addClass("disabled");
var chunks = [];
var blockIndex = blockStart;
while (blockIndex > blockStart - count) {
var chunk = [];
for (var i = blockIndex; (i > (blockIndex - chunkSize) && i > (currentBlockHeight - count)); i--) {
for (var i = blockIndex; (i > (blockIndex - chunkSize) && i > (blockStart - count)); i--) {
chunk.push(i);
}
@ -90,12 +198,13 @@ block endOfBody
chunks.push(chunk);
}
console.log(JSON.stringify(chunks));
//alert(JSON.stringify(chunks));
var results = [];
var statusCallback = function(chunkIndexDone, chunkCount) {
console.log("Done: " + Math.min(((chunkIndexDone + 1) * chunkSize), count) + " of " + count);
//console.log("Done: " + Math.min(((chunkIndexDone + 1) * chunkSize), count) + " of " + count);
var wPercent = `${parseInt(100 * (chunkIndexDone + 1) / parseFloat(chunkCount))}%`;
@ -105,6 +214,7 @@ block endOfBody
var finishedCallback = function() {
$("#time-range-buttons .block-count-btn").removeClass("disabled");
$("#block-selections-buttons .dropdown-item").removeClass("disabled");
var summary = summarizeData(results);
@ -129,6 +239,12 @@ block endOfBody
createGraph("weights-sizes", ["weight", "size"], [summary.weight, summary.size], [blue, green]);
createGraph("sw-txs", ["swtxs"], [summary.swtxs], [blue]);
createGraph("subsidy", ["subsidy"], [summary.subsidy], [blue]);
$("#main-content").show();
$("#progress-wrapper").hide();
@ -188,30 +304,43 @@ block endOfBody
summary.weight = [];
summary.size = [];
summary.swtxs = [];
summary.subsidy = [];
for (var i = results.length - 1; i >= 0; i--) {
summary.avgfeerate.push({x:results[i].height, y:results[i].avgfeerate});
summary.medianfeerate.push({x:results[i].height, y:results[i].feerate_percentiles[2]});
summary.minfeerate.push({x:results[i].height, y:results[i].minfeerate});
summary.maxfeerate.push({x:results[i].height, y:results[i].maxfeerate});
summary.minfee.push({x:results[i].height, y:results[i].minfee});
summary.maxfee.push({x:results[i].height, y:results[i].maxfee});
summary.inputs.push({x:results[i].height, y:results[i].ins});
summary.outputs.push({x:results[i].height, y:results[i].outs});
summary.avgtxsize.push({x:results[i].height, y:results[i].avgtxsize});
summary.mediantxsize.push({x:results[i].height, y:results[i].mediantxsize});
summary.mintxsize.push({x:results[i].height, y:results[i].mintxsize});
summary.maxtxsize.push({x:results[i].height, y:results[i].maxtxsize});
summary.mediantxsize.push({x:results[i].height, y:results[i].mediantxsize});
summary.totaloutput.push({x:results[i].height, y:new Decimal(results[i].total_out).dividedBy(100000000)});
summary.weight.push({x:results[i].height, y:results[i].total_weight});
summary.size.push({x:results[i].height, y:results[i].total_size});
summary.swtxs.push({x:results[i].height, y:results[i].swtxs});
summary.subsidy.push({x:results[i].height, y:results[i].subsidy});
}
return summary;
}
function createGraph(chartid, names, datas, colors) {
console.log(chartid + " - " + JSON.stringify(datas));
var datasets = [];
var yaxes = [];
@ -263,4 +392,10 @@ block endOfBody
}
});
}
if (rpcApiUnsupportedError == null)
script.
$(document).ready(function() {
getData(currentBlockHeight, 144, 15);
});

3
views/includes/debug-overrides.pug

@ -6,7 +6,8 @@
//- utxoSetSummaryPending = false;
// debug as if we don't have result.blockstats (applies to block pages when node version < 0.17.0)
//- result.blockstats = null;
//if (result)
// - result.blockstats = null;
// no networkVolume
//- networkVolume = null;

4
views/includes/index-network-summary.pug

@ -1,5 +1,5 @@
- var colClass = "col-lg-6 px-3";
if (exchangeRates)
if (exchangeRates || networkVolume)
- colClass = "col-lg-4 px-3";
- var utxoCalculatingDesc = "At startup the app pulls a summary of the UTXO set. Until this summary is retrieved this data can't be displayed. Wait for the summary request to your node to return, then refresh this page.";
@ -63,7 +63,7 @@ div.row.index-summary
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-poll.mr-1.summary-icon
i.fas.fa-bullseye.mr-1.summary-icon
span.border-dotted(title="Current fee estimates (using 'estimatesmartfee') for getting a transaction included in 1 block, 6 blocks (1 hr), 144 blocks (1 day), or 1,008 blocks (1 week).", data-toggle="tooltip") Fee Targets
if (false)
small.ml-1 (1/h/d/w)

4
views/includes/tools-card.pug

@ -52,8 +52,8 @@ if (true)
// md: 3 columns (requires separate layout implementation...see below)
// lg, xl: 4 columns
// xm: 2 columns
- var indexLists = [[0, 1, 2], [3, 4, 10], [9, 5, 6], [7, 8]];
- var indexListsMediumWidth = [[0, 1, 2, 3], [4, 10, 9, 5], [6, 7, 8]];
- var indexLists = [[0, 10, 4], [5, 9, 3], [2, 1, 6], [7, 8]];
- var indexListsMediumWidth = [[0, 10, 4, 5], [9, 3, 2, 1], [6, 7, 8]];
// special case for medium-width layout
div.col.d-none.d-md-block.d-lg-none

10
views/index.pug

@ -78,7 +78,7 @@ block content
div.mb-3.pr-xxl-0(class=`col-xxl-${summaryColCount}`)
div.card.shadow-sm(style="height: 100%;")
div.card-body.px-2.px-sm-3
h3.h6 Network Summary
h3.h6.mb-0 Network Summary
hr
include includes/index-network-summary.pug
@ -86,7 +86,7 @@ block content
div.mb-3(class=`col-xxl-${12 - summaryColCount}`)
div.card.shadow-sm(style="height: 100%;")
div.card-body.px-2.px-sm-3
h3.h6 Tools
h3.h6.mb-0 Tools
hr
include includes/tools-card.pug
@ -99,10 +99,10 @@ block content
div.card-body.px-2.px-sm-3
div.row
div.col
h3.h6 Latest Blocks
h3.h6.mb-0 Latest Blocks
div.col.text-right
a(href="/blocks") Browse blocks &raquo;
a(href="/blocks") See more &raquo;
hr
@ -140,7 +140,7 @@ block content
div.card-body.px-2.px-sm-3
div.row
div.col
h3.h6 Transaction Stats
h3.h6.mb-0 Transaction Stats
div.col.text-right
a(href="/tx-stats") See more &raquo;

730
views/mempool-summary.pug

@ -2,16 +2,21 @@ extends layout
block headContent
title Mempool Summary
block content
h1.h3 Mempool Summary
hr
if (false)
pre
code.json.bg-light #{JSON.stringify(mempoolstats, null, 4)}
div#progress-wrapper
div.card.shadow-sm.mb-3
div.card-body
h4.h6 Loading mempool transactions:
span(id="block-progress-text")
div.progress(id="progress-bar", style="height: 7px;")
div.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
if (true)
div(id="main-content", style="display: none;")
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6 Summary
@ -20,213 +25,528 @@ block content
table.table.details-table.mb-0
tr
td.properties-header Transaction Count
td.text-monospace #{getmempoolinfo.size.toLocaleString()}
td.text-monospace(id="tx-count")
tr
- var mem1Data = utils.formatLargeNumber(getmempoolinfo.usage, 2);
- var mem2Data = utils.formatLargeNumber(getmempoolinfo.bytes, 2);
td.properties-header Memory Usage
td.text-monospace
span #{mem1Data[0]}
small #{mem1Data[1].abbreviation}B
span.text-muted (#{mem2Data[0]}
small v#{mem2Data[1].abbreviation}B
span )
td.text-monospace(id="mem-usage")
tr
td.properties-header Total Fees
td.text-monospace
- var currencyValue = mempoolstats["totalFees"];
include includes/value-display.pug
if (getmempoolinfo.size > 0)
tr
td.properties-header Avg Fee
td.text-monospace
- var currencyValue = mempoolstats["averageFee"];
include ./includes/value-display.pug
tr
td.properties-header Avg Fee Rate
td.text-monospace
- var feeRateData = utils.formatCurrencyAmountInSmallestUnits(mempoolstats["averageFeePerByte"], 2);
span #{feeRateData.val}
small #{feeRateData.currencyUnit}/vB
if (getmempoolinfo.size > 0)
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6 Transactions by fee rate
hr
if (false)
#{JSON.stringify(mempoolstats)}
if (true)
- var feeBucketLabels = [("[0 - " + mempoolstats["satoshiPerByteBucketMaxima"][0] + ")")];
each item, index in mempoolstats["satoshiPerByteBuckets"]
if (index > 0 && index < mempoolstats["satoshiPerByteBuckets"].length - 1)
- feeBucketLabels.push(("[" + mempoolstats["satoshiPerByteBucketMaxima"][index - 1] + " - " + mempoolstats["satoshiPerByteBucketMaxima"][index] + ")"));
- feeBucketLabels.push((mempoolstats.satoshiPerByteBucketMaxima[mempoolstats.satoshiPerByteBucketMaxima.length - 1] + "+"));
td.text-monospace(id="total-fees")
- var feeBucketTxCounts = mempoolstats["satoshiPerByteBucketCounts"];
- var totalfeeBuckets = mempoolstats["satoshiPerByteBucketTotalFees"];
canvas.mb-4(id="mempoolBarChart", height="100")
script(src="/js/chart.bundle.min.js", integrity="sha384-qgOtiGNaHh9fVWUnRjyHlV39rfbDcvPPkEzL1RHvsHKbuqUqM6uybNuVnghY2z4/")
script var feeBucketLabels = [];
script var bgColors = [];
each feeBucketLabel, index in feeBucketLabels
- var percentTx = Math.round(100 * feeBucketTxCounts[index] / getmempoolinfo.size).toLocaleString();
script feeBucketLabels.push(["#{feeBucketLabel}","#{feeBucketTxCounts[index]} tx (#{percentTx}%)"]);
script bgColors.push("hsl(#{(333 * index / feeBucketLabels.length)}, 100%, 50%)");
script var feeBucketTxCounts = [#{feeBucketTxCounts}];
script.
var ctx = document.getElementById("mempoolBarChart").getContext('2d');
var mempoolBarChart = new Chart(ctx, {
type: 'bar',
data: {
labels: feeBucketLabels,
datasets: [{
data: feeBucketTxCounts,
backgroundColor: bgColors
}]
},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
tr
td.properties-header Avg Fee
td.text-monospace(id="avg-fee")
tr
td.properties-header Avg Fee Rate
td.text-monospace(id="avg-fee-rate")
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6 Transactions by fee rate
hr
canvas.mb-4(id="mempoolBarChart", height="100")
div.table-responsive
table.table.table-striped.mb-4
thead
tr
th Fee Rate
th.text-right Tx Count
th.text-right Total Fees
th.text-right Avg Fee
th.text-right Avg Fee Rate
tbody(id="fee-rate-table-body")
tr(id="fee-rate-table-row-prototype", style="display: none;")
td.text-monospace.data-label
td.text-monospace.text-right.data-count
td.text-monospace.text-right.data-total-fees
td.text-monospace.text-right.data-avg-fee
td.text-monospace.text-right.data-fee-rate
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6 Transactions by size
hr
canvas.mb-4(id="txSizesBarChart", height="100")
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6 Transactions by age
hr
canvas.mb-4(id="txAgesBarChart", height="100")
block endOfBody
script(src="/js/chart.bundle.min.js", integrity="sha384-qgOtiGNaHh9fVWUnRjyHlV39rfbDcvPPkEzL1RHvsHKbuqUqM6uybNuVnghY2z4/")
script(src='/js/decimal.js')
script.
var txidChunks = !{JSON.stringify(mempooltxidChunks)};
var satoshiPerByteBucketMaxima = !{JSON.stringify(satoshiPerByteBucketMaxima)};
$(document).ready(function() {
loadMempool(txidChunks, 25, txidChunks.length * 25);
});
function loadMempool(txidChunks, chunkSize, count) {
var chunkStrs = [];
for (var i = 0; i < txidChunks.length; i++) {
var txidChunk = txidChunks[i];
var chunkStr = "";
for (var j = 0; j < txidChunk.length; j++) {
if (j > 0) {
chunkStr += ",";
}
chunkStr += txidChunk[j];
}
chunkStrs.push(chunkStr);
}
//alert(JSON.stringify(chunks));
var results = [];
var statusCallback = function(chunkIndexDone, chunkCount) {
//console.log("Done: " + Math.min(((chunkIndexDone + 1) * chunkSize), count) + " of " + count);
var wPercent = `${parseInt(100 * (chunkIndexDone + 1) / parseFloat(chunkCount))}%`;
$("#data-progress").css("width", wPercent);
$("#block-progress-text").text(`${Math.min(((chunkIndexDone + 1) * chunkSize), count).toLocaleString()} of ${count.toLocaleString()} (${wPercent})`);
};
var finishedCallback = function() {
var summary = summarizeData(results);
var feeRateGraphData = buildFeeRateGraphData(summary);
var txSizeGraphData = buildTxSizeGraphData(summary);
var txAgeGraphData = buildTxAgeGraphData(summary);
//console.log(JSON.stringify(summary));
$("#tx-count").text(summary.count.toLocaleString());
$("#mem-usage").text(summary.totalBytes.toLocaleString());
$("#total-fees").text(summary.totalFees);
$("#avg-fee").text(summary.averageFee);
$("#avg-fee-rate").text(summary.averageFeePerByte);
$.ajax({
url: `/api/utils/formatLargeNumber/${summary.totalBytes},2`
}).done(function(result) {
$("#mem-usage").html(`<span>${result[0]} <small>${result[1].abbreviation}B</small></span>`);
});
updateCurrencyValue($("#total-fees"), summary.totalFees);
updateCurrencyValue($("#avg-fee"), summary.averageFee);
updateFeeRateValue($("#avg-fee-rate"), summary.averageFeePerByte, 2);
//$("#summary-json").text(JSON.stringify(summary, null, 4));
// fee rate chart
var ctx1 = document.getElementById("mempoolBarChart").getContext('2d');
var mempoolBarChart = new Chart(ctx1, {
type: 'bar',
data: {
labels: feeRateGraphData.feeBucketLabels,
datasets: [{
data: feeRateGraphData.feeBucketTxCounts,
backgroundColor: feeRateGraphData.bgColors
}]
},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
});
div(class="table-responsive")
table(class="table table-striped mb-4")
thead
tr
th Fee Rate
th(class="text-right") Tx Count
th(class="text-right") Total Fees
th(class="text-right") Avg Fee
th(class="text-right") Avg Fee Rate
tbody
each item, index in mempoolstats["satoshiPerByteBuckets"]
tr
td.text-monospace #{mempoolstats["satoshiPerByteBucketLabels"][index]}
td.text-monospace.text-right #{item["count"].toLocaleString()}
td.text-monospace.text-right
- var currencyValue = item["totalFees"];
include ./includes/value-display.pug
if (item["totalBytes"] > 0)
- var avgFee = item["totalFees"] / item["count"];
- var avgFeeRate = item["totalFees"] / item["totalBytes"];
td.text-monospace.text-right
- var currencyValue = avgFee;
include ./includes/value-display.pug
td.text-monospace.text-right
- var feeRateData = utils.formatCurrencyAmountInSmallestUnits(avgFeeRate, 2);
span #{feeRateData.val}
small #{feeRateData.currencyUnit}/vB
else
td.text-monospace.text-right -
td.text-monospace.text-right -
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6 Transactions by size
hr
canvas.mb-4(id="txSizesBarChart", height="100")
script var sizeBucketLabels = [];
script var bgColors = [];
each sizeBucketLabel, index in mempoolstats["sizeBucketLabels"]
- var percentTx = Math.round(100 * mempoolstats["sizeBucketTxCounts"][index] / getmempoolinfo.size).toLocaleString();
script sizeBucketLabels.push(["#{sizeBucketLabel} bytes","#{mempoolstats["sizeBucketTxCounts"][index]} tx (#{percentTx}%)"]);
script bgColors.push("hsl(#{(333 * index / mempoolstats["sizeBucketLabels"].length)}, 100%, 50%)");
script var sizeBucketTxCounts = [#{mempoolstats["sizeBucketTxCounts"]}];
script.
var ctx = document.getElementById("txSizesBarChart").getContext('2d');
var txSizesBarChart = new Chart(ctx, {
type: 'bar',
data: {
labels: sizeBucketLabels,
datasets: [{
data: sizeBucketTxCounts,
backgroundColor: bgColors
}]
},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}]
}
}
});
// tx size chart
var ctx2 = document.getElementById("txSizesBarChart").getContext('2d');
var txSizesBarChart = new Chart(ctx2, {
type: 'bar',
data: {
labels: txSizeGraphData.sizeBucketLabels,
datasets: [{
data: txSizeGraphData.sizeBucketTxCounts,
backgroundColor: txSizeGraphData.bgColors
}]
},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}
});
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
h3.h6 Transactions by age
hr
canvas.mb-4(id="txAgesBarChart", height="100")
script var ageBucketLabels = [];
script var bgColors = [];
each ageBucketLabel, index in mempoolstats["ageBucketLabels"]
- var percentTx = Math.round(100 * mempoolstats["ageBucketTxCounts"][index] / getmempoolinfo.size).toLocaleString();
script ageBucketLabels.push(["#{ageBucketLabel}","#{mempoolstats["ageBucketTxCounts"][index]} tx (#{percentTx}%)"]);
script bgColors.push("hsl(#{(333 * index / mempoolstats["ageBucketLabels"].length)}, 100%, 50%)");
script var ageBucketTxCounts = [#{mempoolstats["ageBucketTxCounts"]}];
script.
var ctx = document.getElementById("txAgesBarChart").getContext('2d');
var txAgesBarChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ageBucketLabels,
datasets: [{
data: ageBucketTxCounts,
backgroundColor: bgColors
}]
},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}]
}
}
});
// tx age chart
var ctx3 = document.getElementById("txAgesBarChart").getContext('2d');
var txSizesBarChart = new Chart(ctx3, {
type: 'bar',
data: {
labels: txAgeGraphData.ageBucketLabels,
datasets: [{
data: txAgeGraphData.ageBucketTxCounts,
backgroundColor: txAgeGraphData.bgColors
}]
},
options: {
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}
});
}]
}
}
});
// fee rate table
for (var i = 0; i < summary.satoshiPerByteBuckets.length; i++) {
var item = summary.satoshiPerByteBuckets[i];
var row = $("#fee-rate-table-row-prototype").clone();
row.attr("id", null);
row.addClass("fee-rate-table-row");
row.find(".data-label").text(summary.satoshiPerByteBucketLabels[i]);
row.find(".data-count").text(item.count.toLocaleString());
row.find(".data-total-fees").text(item.count > 0 ? item.totalFees : "-");
row.find(".data-avg-fee").text(item.count > 0 ? item.totalFees / item.count : "-");
row.find(".data-fee-rate").text("-");
if (item.count > 0) {
updateCurrencyValue(row.find(".data-total-fees"), item.totalFees);
updateCurrencyValue(row.find(".data-avg-fee"), item.totalFees / item.count);
updateFeeRateValue(row.find(".data-fee-rate"), item.totalFees / item.totalBytes, 2);
}
row.show();
$("#fee-rate-table-body").append(row);
}
$("#main-content").show();
$("#progress-wrapper").hide();
};
getBlockData(results, chunkStrs, 0, statusCallback, finishedCallback);
}
function updateCurrencyValue(element, val) {
$.ajax({
url: `/snippet/formatCurrencyAmount/${val}`
}).done(function(result) {
element.html(result);
$('[data-toggle="tooltip"]').tooltip();
});
}
function updateFeeRateValue(element, val, digits) {
$.ajax({
url: `/api/utils/formatCurrencyAmountInSmallestUnits/${val},${digits}`
}).done(function(result) {
element.html(`<span>${result.val} <small>${result.currencyUnit}/vB</small></span>`);
});
}
function getBlockData(results, chunkStrs, chunkIndex, statusCallback, finishedCallback) {
if (chunkIndex > chunkStrs.length - 1) {
finishedCallback();
return;
}
var url = `/api/mempool-txs/${chunkStrs[chunkIndex]}`;
//console.log(url);
$.ajax({
url: url
}).done(function(result) {
for (var i = 0; i < result.length; i++) {
results.push(result[i]);
}
statusCallback(chunkIndex, chunkStrs.length);
getBlockData(results, chunkStrs, chunkIndex + 1, statusCallback, finishedCallback);
});
}
function buildFeeRateGraphData(summary) {
var feeBucketLabels = [("[0 - " + summary["satoshiPerByteBucketMaxima"][0] + ")")];
for (var i = 0; i < summary["satoshiPerByteBuckets"].length; i++) {
var item = summary["satoshiPerByteBuckets"][i];
if (i > 0 && i < summary["satoshiPerByteBuckets"].length - 1) {
feeBucketLabels.push(("[" + summary["satoshiPerByteBucketMaxima"][i - 1] + " - " + summary["satoshiPerByteBucketMaxima"][i] + ")"));
}
}
feeBucketLabels.push((summary.satoshiPerByteBucketMaxima[summary.satoshiPerByteBucketMaxima.length - 1] + "+"));
var feeBucketTxCounts = summary["satoshiPerByteBucketCounts"];
var totalfeeBuckets = summary["satoshiPerByteBucketTotalFees"];
var graphData = {feeBucketLabels:[], bgColors:[], feeBucketTxCounts:feeBucketTxCounts};
for (var i = 0; i < feeBucketLabels.length; i++) {
var feeBucketLabel = feeBucketLabels[i];
var percentTx = Math.round(100 * feeBucketTxCounts[i] / summary.count).toLocaleString();
graphData.feeBucketLabels.push([feeBucketLabel, `${feeBucketTxCounts[i]} tx (${percentTx}%)`]);
graphData.bgColors.push(`hsl(${(333 * i / feeBucketLabels.length)}, 100%, 50%)`);
}
return graphData;
}
function buildTxSizeGraphData(summary) {
var sizeBucketLabels = [];
var bgColors = [];
for (var i = 0; i < summary.sizeBucketLabels.length; i++) {
var sizeBucketLabel = summary.sizeBucketLabels[i];
var percentTx = Math.round(100 * summary.sizeBucketTxCounts[i] / summary.count).toLocaleString();
sizeBucketLabels.push([`${sizeBucketLabel} bytes`, `${summary.sizeBucketTxCounts[i]} tx (${percentTx}%)`]);
bgColors.push(`hsl(${(333 * i / summary.sizeBucketLabels.length)}, 100%, 50%)`);
}
return {
sizeBucketLabels: sizeBucketLabels,
bgColors: bgColors,
sizeBucketTxCounts: summary.sizeBucketTxCounts
};
}
function buildTxAgeGraphData(summary) {
var ageBucketLabels = [];
var bgColors = [];
for (var i = 0; i < summary.ageBucketLabels.length; i++) {
var ageBucketLabel = summary.ageBucketLabels[i];
var percentTx = Math.round(100 * summary.ageBucketTxCounts[i] / summary.count).toLocaleString();
ageBucketLabels.push([`${ageBucketLabel}`, `${summary.ageBucketTxCounts[i]} tx (${percentTx}%)`]);
bgColors.push(`hsl(${(333 * i / summary.ageBucketLabels.length)}, 100%, 50%)`);
}
return {
ageBucketLabels: ageBucketLabels,
bgColors: bgColors,
ageBucketTxCounts: summary.ageBucketTxCounts
};
}
function summarizeData(rawdata) {
var summary = [];
var maxFee = 0;
var maxFeePerByte = 0;
var maxAge = 0;
var maxSize = 0;
var ages = [];
var sizes = [];
for (var i = 0; i < rawdata.length; i++) {
var txMempoolInfo = rawdata[i].entry;
var fee = txMempoolInfo.modifiedfee;
var size = txMempoolInfo.vsize ? txMempoolInfo.vsize : txMempoolInfo.size;
var feePerByte = txMempoolInfo.modifiedfee / size;
var age = Date.now() / 1000 - txMempoolInfo.time;
if (fee > maxFee) {
maxFee = txMempoolInfo.modifiedfee;
}
if (feePerByte > maxFeePerByte) {
maxFeePerByte = txMempoolInfo.modifiedfee / size;
}
ages.push({age:age, txid:"abc"});
sizes.push({size:size, txid:"abc"});
if (age > maxAge) {
maxAge = age;
}
if (size > maxSize) {
maxSize = size;
}
}
ages.sort(function(a, b) {
if (a.age != b.age) {
return b.age - a.age;
} else {
return a.txid.localeCompare(b.txid);
}
});
sizes.sort(function(a, b) {
if (a.size != b.size) {
return b.size - a.size;
} else {
return a.txid.localeCompare(b.txid);
}
});
maxSize = 2000;
var bucketCount = satoshiPerByteBucketMaxima.length + 1;
var satoshiPerByteBuckets = [];
var satoshiPerByteBucketLabels = [];
satoshiPerByteBucketLabels[0] = ("[0 - " + satoshiPerByteBucketMaxima[0] + ")");
for (var i = 0; i < bucketCount; i++) {
satoshiPerByteBuckets[i] = {"count":0, "totalFees":0, "totalBytes":0};
if (i > 0 && i < bucketCount - 1) {
satoshiPerByteBucketLabels[i] = ("[" + satoshiPerByteBucketMaxima[i - 1] + " - " + satoshiPerByteBucketMaxima[i] + ")");
}
}
var ageBucketCount = 100;
var ageBucketTxCounts = [];
var ageBucketLabels = [];
var sizeBucketCount = 100;
var sizeBucketTxCounts = [];
var sizeBucketLabels = [];
for (var i = 0; i < ageBucketCount; i++) {
var rangeMin = i * maxAge / ageBucketCount;
var rangeMax = (i + 1) * maxAge / ageBucketCount;
ageBucketTxCounts.push(0);
if (maxAge > 600) {
var rangeMinutesMin = new Decimal(rangeMin / 60).toFixed(1);
var rangeMinutesMax = new Decimal(rangeMax / 60).toFixed(1);
ageBucketLabels.push(rangeMinutesMin + " - " + rangeMinutesMax + " min");
} else {
ageBucketLabels.push(parseInt(rangeMin) + " - " + parseInt(rangeMax) + " sec");
}
}
for (var i = 0; i < sizeBucketCount; i++) {
sizeBucketTxCounts.push(0);
if (i == sizeBucketCount - 1) {
sizeBucketLabels.push(parseInt(i * maxSize / sizeBucketCount) + "+");
} else {
sizeBucketLabels.push(parseInt(i * maxSize / sizeBucketCount) + " - " + parseInt((i + 1) * maxSize / sizeBucketCount));
}
}
satoshiPerByteBucketLabels[bucketCount - 1] = (satoshiPerByteBucketMaxima[satoshiPerByteBucketMaxima.length - 1] + "+");
var summary = {
"count":0,
"totalFees":0,
"totalBytes":0,
"satoshiPerByteBuckets":satoshiPerByteBuckets,
"satoshiPerByteBucketLabels":satoshiPerByteBucketLabels,
"ageBucketTxCounts":ageBucketTxCounts,
"ageBucketLabels":ageBucketLabels,
"sizeBucketTxCounts":sizeBucketTxCounts,
"sizeBucketLabels":sizeBucketLabels
};
for (var x = 0; x < rawdata.length; x++) {
var txMempoolInfo = rawdata[x].entry;
var fee = txMempoolInfo.modifiedfee;
var size = txMempoolInfo.vsize ? txMempoolInfo.vsize : txMempoolInfo.size;
var feePerByte = txMempoolInfo.modifiedfee / size;
var satoshiPerByte = feePerByte * 100000000; // TODO: magic number - replace with coinConfig.baseCurrencyUnit.multiplier
var age = Date.now() / 1000 - txMempoolInfo.time;
var addedToBucket = false;
for (var i = 0; i < satoshiPerByteBucketMaxima.length; i++) {
if (satoshiPerByteBucketMaxima[i] > satoshiPerByte) {
satoshiPerByteBuckets[i]["count"]++;
satoshiPerByteBuckets[i]["totalFees"] += fee;
satoshiPerByteBuckets[i]["totalBytes"] += size;
addedToBucket = true;
break;
}
}
if (!addedToBucket) {
satoshiPerByteBuckets[bucketCount - 1]["count"]++;
satoshiPerByteBuckets[bucketCount - 1]["totalFees"] += fee;
satoshiPerByteBuckets[bucketCount - 1]["totalBytes"] += size;
}
summary["count"]++;
summary["totalFees"] += txMempoolInfo.modifiedfee;
summary["totalBytes"] += size;
var ageBucketIndex = Math.min(ageBucketCount - 1, parseInt(age / (maxAge / ageBucketCount)));
var sizeBucketIndex = Math.min(sizeBucketCount - 1, parseInt(size / (maxSize / sizeBucketCount)));
ageBucketTxCounts[ageBucketIndex]++;
sizeBucketTxCounts[sizeBucketIndex]++;
}
summary["averageFee"] = summary["totalFees"] / summary["count"];
summary["averageFeePerByte"] = summary["totalFees"] / summary["totalBytes"];
summary["satoshiPerByteBucketMaxima"] = satoshiPerByteBucketMaxima;
summary["satoshiPerByteBucketCounts"] = [];
summary["satoshiPerByteBucketTotalFees"] = [];
for (var i = 0; i < bucketCount; i++) {
summary["satoshiPerByteBucketCounts"].push(summary["satoshiPerByteBuckets"][i]["count"]);
summary["satoshiPerByteBucketTotalFees"].push(summary["satoshiPerByteBuckets"][i]["totalFees"]);
}
return summary;
}

224
views/mining-summary.pug

@ -7,71 +7,105 @@ block content
h1.h3 Mining Summary
hr
div.mb-4(id="time-range-buttons")
h3.h5 Block Count
div.btn-group
a.btn.btn-primary.block-count-btn(href="javascript:void(0)", data-blockCount="144") 144
small.ml-2 (~1d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="432") 432
small.ml-2 (~3d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="1008") 1,008
small.ml-2 (~7d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="4320") 4,320
small.ml-2 (~30d)
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Selected Blocks
hr
div.clearfix
div.float-left.mr-4
div(id="time-range-buttons")
h3.h6 Latest N Blocks
div.btn-group
a.btn.btn-primary.block-count-btn(href="javascript:void(0)", data-blockCount="144") 144
small.ml-2 (~1d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="432") 432
small.ml-2 (~3d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="1008") 1,008
small.ml-2 (~7d)
a.btn.btn-outline-primary.block-count-btn(href="javascript:void(0)", data-blockCount="4320") 4,320
small.ml-2 (~30d)
div.float-left.mr-4
div(id="block-selections-buttons")
h3.h6 Preconfigured
div.dropdown
button.btn.btn-outline-primary.dropdown-toggle(type="button", id="preconfigured-dropdown", data-toggle="dropdown", aria-haspopup="true", aria-expanded="false") Selections
div.dropdown-menu(aria-labelledby="preconfigured-dropdown")
a.dropdown-item(href="javascript:void(0)", data-blocks="0-199") First 200 Blocks
a.dropdown-item(href="javascript:void(0)", data-blocks="209900-210100") First Halving
a.dropdown-item(href="javascript:void(0)", data-blocks="419900-420100") Second Halving
a.dropdown-item(href="javascript:void(0)", data-blocks="481724-481924") SegWit Activation Window
div.float-left
div(id="time-range-buttons")
h3.h6 Custom Range
form.form-inline.mr-3(id="custom-range-form")
div.input-group
input.form-control(type="text", id="custom-range-start", placeholder="min height", style="width: 125px;")
input.form-control(type="text", id="custom-range-end", placeholder="max height", style="width: 125px;")
div.input-group-append
button.btn.btn-primary(type="submit") Go
div#progress-wrapper
h4.h6 Loading blocks:
span(id="block-progress-text")
div.progress(id="progress-bar", style="height: 7px; margin-bottom: 200px;")
div.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
div.card.shadow-sm.mb-3
div.card-body
h4.h6 Loading blocks:
span(id="block-progress-text")
div.progress(id="progress-bar", style="height: 7px;")
div.progress-bar(id="data-progress", role="progressbar", aria-valuenow="0", aria-valuemin="0" ,aria-valuemax="100")
div.table-responsive.mb-5(id="summary-table", style="display: none;")
table.table.table-striped.mb-0
thead
tr
th.data-header Miner
th.data-header.text-right Blocks
th.data-header.text-right Transactions
th.data-header.text-right Block Rewards
th.data-header.text-right Fees Collected
tbody(id="summary-table-body")
tr(id="miner-summary-prototype", style="display: none;")
td.data-cell.text-monospace.miner-name
td.data-cell.text-monospace.text-right
span.miner-block-count
small.text-muted.miner-block-percent.ml-2
td.data-cell.text-monospace.text-right
span.miner-tx-count
small.text-muted.miner-tx-percent.ml-2
td.data-cell.text-monospace.text-right
span.miner-subsidy
small.text-muted.miner-subsidy-percent.ml-2
//- var currencyValue = new Decimal(summary.totalSubsidiesCollected);
//include ./includes/value-display.pug
td.data-cell.text-monospace.text-right
span.miner-fees
small.text-muted.miner-fees-percent.ml-2
//- var currencyValue = new Decimal(summary.totalFeesCollected);
//include ./includes/value-display.pug
tr(id="miner-summary-totals", style="display: none;")
th.data-cell.text-monospace.miner-name Total
th.data-cell.text-monospace.text-right
span.miner-block-count
small.text-muted.miner-block-percent.ml-2 (100%)
th.data-cell.text-monospace.text-right
span.miner-tx-count
small.text-muted.miner-tx-percent.ml-2 (100%)
th.data-cell.text-monospace.text-right
span.miner-subsidy
//- var currencyValue = new Decimal(summary.totalSubsidiesCollected);
//include ./includes/value-display.pug
th.data-cell.text-monospace.text-right
span.miner-fees
//- var currencyValue = new Decimal(summary.totalFeesCollected);
//include ./includes/value-display.pug
div.card.shadow-sm.mb-3(id="summary-table", style="display: none;")
div.card-body
div.table-responsive
table.table.table-striped.mb-0
thead
tr
th.data-header Miner
th.data-header.text-right Blocks
th.data-header.text-right Transactions
th.data-header.text-right Block Rewards
th.data-header.text-right Fees Collected
tbody(id="summary-table-body")
tr(id="miner-summary-prototype", style="display: none;")
td.data-cell.text-monospace.miner-name
td.data-cell.text-monospace.text-right
span.miner-block-count
small.text-muted.miner-block-percent.ml-2
td.data-cell.text-monospace.text-right
span.miner-tx-count
small.text-muted.miner-tx-percent.ml-2
td.data-cell.text-monospace.text-right
span.miner-subsidy
small.text-muted.miner-subsidy-percent.ml-2
//- var currencyValue = new Decimal(summary.totalSubsidiesCollected);
//include ./includes/value-display.pug
td.data-cell.text-monospace.text-right
span.miner-fees
small.text-muted.miner-fees-percent
//- var currencyValue = new Decimal(summary.totalFeesCollected);
//include ./includes/value-display.pug
tr(id="miner-summary-totals", style="display: none;")
th.data-cell.text-monospace.miner-name Total
th.data-cell.text-monospace.text-right
span.miner-block-count
small.text-muted.miner-block-percent.ml-2 (100%)
th.data-cell.text-monospace.text-right
span.miner-tx-count
small.text-muted.miner-tx-percent.ml-2 (100%)
th.data-cell.text-monospace.text-right
span.miner-subsidy
//- var currencyValue = new Decimal(summary.totalSubsidiesCollected);
//include ./includes/value-display.pug
th.data-cell.text-monospace.text-right
span.miner-fees
//- var currencyValue = new Decimal(summary.totalFeesCollected);
//include ./includes/value-display.pug
if (false)
pre
@ -86,6 +120,7 @@ block endOfBody
// highlight current selection
$("#time-range-buttons .block-count-btn").removeClass("btn-primary").addClass("btn-outline-primary");
$(this).addClass("btn-primary").removeClass("btn-outline-primary");
$("#preconfigured-dropdown").removeClass("btn-primary").addClass("btn-outline-primary");
var blockCount = parseInt($(this).attr("data-blockCount"));
@ -100,17 +135,60 @@ block endOfBody
getData(currentBlockHeight, blockCount, 15);
});
$("#block-selections-buttons .dropdown-item").on("click", function() {
// highlight current selection
$("#time-range-buttons .block-count-btn").removeClass("btn-primary").addClass("btn-outline-primary");
$("#preconfigured-dropdown").removeClass("btn-outline-primary").addClass("btn-primary");
var blocks = $(this).attr("data-blocks");
var bStartEnd = blocks.split("-");
var bStart = parseInt(bStartEnd[0]);
var bEnd = parseInt(bStartEnd[1]);
$(".miner-summary-row").remove();
$("#data-progress").css("width", "0%");
$("#block-progress-text").text("");
$("#main-content").hide();
$("#progress-wrapper").show();
getData(bEnd, bEnd - bStart + 1, 15);
});
$("#custom-range-form").on("submit", function() {
// highlight current selection
$("#time-range-buttons .block-count-btn").removeClass("btn-primary").addClass("btn-outline-primary");
$("#preconfigured-dropdown").removeClass("btn-primary").addClass("btn-outline-primary");
var bStart = parseInt($("#custom-range-start").val());
var bEnd = parseInt($("#custom-range-end").val());
$(".miner-summary-row").remove();
$("#data-progress").css("width", "0%");
$("#block-progress-text").text("");
$("#main-content").hide();
$("#progress-wrapper").show();
getData(bEnd, bEnd - bStart + 1, 15);
return false;
});
getData(currentBlockHeight, 144, 15);
});
function getData(blockStart, count, chunkSize) {
$("#time-range-buttons .block-count-btn").addClass("disabled");
$("#block-selections-buttons .dropdown-item").addClass("disabled");
var chunks = [];
var blockIndex = blockStart;
while (blockIndex > blockStart - count) {
var chunk = [];
for (var i = blockIndex; (i > (blockIndex - chunkSize) && i > (currentBlockHeight - count)); i--) {
for (var i = blockIndex; (i > (blockIndex - chunkSize) && i > (blockStart - count)); i--) {
chunk.push(i);
}
@ -118,6 +196,7 @@ block endOfBody
chunks.push(chunk);
}
console.log(JSON.stringify(chunks));
//alert(JSON.stringify(chunks));
var results = [];
@ -133,6 +212,7 @@ block endOfBody
var finishedCallback = function() {
$("#time-range-buttons .block-count-btn").removeClass("disabled");
$("#block-selections-buttons .dropdown-item").removeClass("disabled");
console.log(JSON.stringify(results));
@ -159,7 +239,15 @@ block endOfBody
row.find(".miner-subsidy-percent").text(`(${new Decimal(minerSummary.totalSubsidiesCollected).dividedBy(summary.overallSummary.totalSubsidiesCollected).times(100).toDecimalPlaces(1)}%)`);
row.find(".miner-fees").text(minerSummary.totalFeesCollected.toLocaleString());
row.find(".miner-fees-percent").text(`(${new Decimal(minerSummary.totalFeesCollected).dividedBy(summary.overallSummary.totalFeesCollected).times(100).toDecimalPlaces(1)}%)`);
if (summary.overallSummary.totalFeesCollected > 0) {
row.find(".miner-fees-percent").text(` (${new Decimal(minerSummary.totalFeesCollected).dividedBy(summary.overallSummary.totalFeesCollected).times(100).toDecimalPlaces(1)}%)`);
}
updateCurrencyValue(row.find(".miner-subsidy"), minerSummary.totalSubsidiesCollected);
updateCurrencyValue(row.find(".miner-fees"), minerSummary.totalFeesCollected);
row.show();
@ -213,6 +301,16 @@ block endOfBody
});
}
function updateCurrencyValue(element, val) {
$.ajax({
url: `/snippet/formatCurrencyAmount/${val}`
}).done(function(result) {
element.html(result);
$('[data-toggle="tooltip"]').tooltip();
});
}
function summarizeBlockData(blocks) {
var summariesByMiner = {};
var minerNamesSortedByBlockCount = [];

14
views/transaction.pug

@ -185,20 +185,16 @@ block content
- var currencyValue = new Decimal(blockRewardMax).minus(totalOutputValue);
include includes/value-display.pug
- var minerInfo = utils.getMinerFromCoinbaseTx(result.getrawtransaction);
if (minerInfo)
- var minerInfo = utils.getMinerFromCoinbaseTx(result.getrawtransaction);
div.row
div.summary-table-label Miner
div.summary-table-content.text-monospace
if (minerInfo)
span #{minerInfo.name}
if (minerInfo.identifiedBy)
span(data-toggle="tooltip", title=("Identified by: " + minerInfo.identifiedBy))
i.fas.fa-info-circle
if (minerInfo.identifiedBy)
small.data-tag.bg-primary(data-toggle="tooltip", title=("Identified by: " + minerInfo.identifiedBy)) #{minerInfo.name}
else
span ?
span(data-toggle="tooltip", title="Unable to identify miner")
i.fas.fa-info-circle
small.data-tag.bg-primary #{minerInfo.name}
else

Loading…
Cancel
Save