Browse Source

More improvements:

- Include "Volume (24hr)" in homepage summary
- Include block output volume in blocks list (0.17.0+)
- Show "%Full" in blocks lists (with fullness icon) instead of weight with progress bar
- Hide "Date" column on homepage blocks list (Age+TTM) is plenty for the recent blocks)
- Fix to pull UTXO set after RPC connection is established
- Fix for abbreviating "month" to "mo" instead of "m" which can be confused with minutes
- Tweak width of data columns on 1200 screens to avoid some wrapping of title labels
- More consistently "smallify" display of units
- Armor on block-content template for missing blockstats values (identified issue by looking at genesis block, added manual data for that block since RPC returns error, but armor is still probably valuable)
- Tweaks to timestamps "time ago" displays in several places trying to make more consistent and user friendly
- Explicit acknowledgement of "nonstandard" output type
master
Dan Janosik 5 years ago
parent
commit
dc70559b08
No known key found for this signature in database GPG Key ID: C6F8CE9FFDB2CED2
  1. 11
      CHANGELOG.md
  2. 75
      app.js
  3. 24
      app/api/coreApi.js
  4. 27
      app/api/rpcApi.js
  5. 39
      app/coins/btc.js
  6. 4
      app/utils.js
  7. 8
      public/css/styling.css
  8. BIN
      public/img/block-fullness-0.png
  9. BIN
      public/img/block-fullness-1.png
  10. BIN
      public/img/block-fullness-2.png
  11. BIN
      public/img/block-fullness-3.png
  12. 83
      routes/baseActionsRouter.js
  13. 4
      views/address.pug
  14. 208
      views/includes/block-content.pug
  15. 91
      views/includes/blocks-list.pug
  16. 5
      views/includes/debug-overrides.pug
  17. 87
      views/includes/index-network-summary.pug
  18. 16
      views/includes/time-ago-text.pug
  19. 12
      views/includes/timestamp-human.pug
  20. 4
      views/includes/transaction-io-details.pug
  21. 29
      views/transaction.pug

11
CHANGELOG.md

@ -11,13 +11,14 @@
* UTXO set size
* Total coins in circulation
* Market cap
* 24-hour network volume (sum of outputs)
* Tweaks to data in blocks lists:
* Simpler timestamp formatting for easy reading
* Include "Time-to-Mine" (TTM) for each block (with green/red highlighting for "fast"/"slow" (<5min/>15min) blocks)
* Display average fee in sat/vB
* Add total fees display
* Demote display of "block size" value to hover
* Show weight in kWu instead of Wu
* Add total fees
* Add output volume (if `getblockstats` rpc call is supported, i.e. 0.17.0+)
* Show %Full instead of weight/size
* New data in "Summary" on Block pages (supported for bitcoind v0.17.0+)
* Fee percentiles
* Min / Max fees
@ -31,7 +32,9 @@
* Configurable UI "sub-header" links
* Start of RPC API versioning support
* Tweaked styling
* Remove "Bitcoin Explorer" H1 from homepage (it's redundant)
* Homepage tweaks
* Remove "Bitcoin Explorer" H1 (it's redundant)
* Hide the "Date" (timestamp) column for recent blocks (the Age+TTM is more valuable)
* Updated miner configs
#### v1.1.9

75
app.js

@ -258,6 +258,15 @@ function onRpcConnectionVerified(getnetworkinfo, getblockchaininfo) {
// refresh exchange rate periodically
setInterval(utils.refreshExchangeRates, 1800000);
}
// UTXO pull
refreshUtxoSetSummary();
setInterval(refreshUtxoSetSummary, 30 * 60 * 1000);
// 1d / 7d volume
refreshNetworkVolumes();
setInterval(refreshNetworkVolumes, 30 * 60 * 1000);
}
function refreshUtxoSetSummary() {
@ -282,6 +291,67 @@ function refreshUtxoSetSummary() {
});
}
function refreshNetworkVolumes() {
var cutoff1d = new Date().getTime() - (60 * 60 * 24 * 1000);
var cutoff7d = new Date().getTime() - (60 * 60 * 24 * 7 * 1000);
coreApi.getBlockchainInfo().then(function(result) {
var promises = [];
var blocksPerDay = 144 + 20; // 20 block padding
for (var i = 0; i < (blocksPerDay * 1); i++) {
promises.push(coreApi.getBlockStatsByHeight(result.blocks - i));
}
var startBlock = result.blocks;
var endBlock1d = result.blocks;
var endBlock7d = result.blocks;
var endBlockTime1d = 0;
var endBlockTime7d = 0;
Promise.all(promises).then(function(results) {
var volume1d = new Decimal(0);
var volume7d = new Decimal(0);
var blocks1d = 0;
var blocks7d = 0;
if (results && results.length > 0 && results[0] != null) {
for (var i = 0; i < results.length; i++) {
if (results[i].time * 1000 > cutoff1d) {
volume1d = volume1d.plus(new Decimal(results[i].total_out));
blocks1d++;
endBlock1d = results[i].height;
endBlockTime1d = results[i].time;
}
if (results[i].time * 1000 > cutoff7d) {
volume7d = volume7d.plus(new Decimal(results[i].total_out));
blocks7d++;
endBlock7d = results[i].height;
endBlockTime7d = results[i].time;
}
}
volume1d = volume1d.dividedBy(coinConfig.baseCurrencyUnit.multiplier);
volume7d = volume7d.dividedBy(coinConfig.baseCurrencyUnit.multiplier);
global.networkVolume = {d1:{amt:volume1d, blocks:blocks1d, startBlock:startBlock, endBlock:endBlock1d, startTime:results[0].time, endTime:endBlockTime1d}};
debugLog(`Network volume: ${JSON.stringify(global.networkVolume)}`);
} else {
debugLog("Unable to load network volume, likely due to bitcoind version older than 0.17.0 (the first version to support getblockstats).");
}
});
});
}
app.onStartup = function() {
global.config = config;
@ -381,10 +451,6 @@ app.continueStartup = function() {
utils.logMemoryUsage();
setInterval(utils.logMemoryUsage, 5000);
refreshUtxoSetSummary();
setInterval(refreshUtxoSetSummary, 30 * 60 * 1000);
};
app.use(function(req, res, next) {
@ -416,6 +482,7 @@ app.use(function(req, res, next) {
res.locals.exchangeRates = global.exchangeRates;
res.locals.utxoSetSummary = global.utxoSetSummary;
res.locals.utxoSetSummaryPending = global.utxoSetSummaryPending;
res.locals.networkVolume = global.networkVolume;
res.locals.host = req.session.host;
res.locals.port = req.session.port;

24
app/api/coreApi.js

@ -185,6 +185,12 @@ function getBlockStats(hash) {
});
}
function getBlockStatsByHeight(height) {
return tryCacheThenRpcApi(miscCache, "getBlockStatsByHeight-" + height, 1000 * 60 * 1000, function() {
return rpcApi.getBlockStatsByHeight(height);
});
}
function getUtxoSetSummary() {
return tryCacheThenRpcApi(miscCache, "getUtxoSetSummary", 15 * 60 * 1000, rpcApi.getUtxoSetSummary);
}
@ -626,6 +632,22 @@ function getBlocksByHeight(blockHeights) {
});
}
function getBlocksStatsByHeight(blockHeights) {
return new Promise(function(resolve, reject) {
var promises = [];
for (var i = 0; i < blockHeights.length; i++) {
promises.push(getBlockStatsByHeight(blockHeights[i]));
}
Promise.all(promises).then(function(results) {
resolve(results);
}).catch(function(err) {
reject(err);
});
});
}
function getBlockByHash(blockHash) {
return tryCacheThenRpcApi(blockCache, "getBlockByHash-" + blockHash, 3600000, function() {
return rpcApi.getBlockByHash(blockHash);
@ -1000,4 +1022,6 @@ module.exports = {
getUtxoSetSummary: getUtxoSetSummary,
getNetworkHashrate: getNetworkHashrate,
getBlockStats: getBlockStats,
getBlockStatsByHeight: getBlockStatsByHeight,
getBlocksStatsByHeight: getBlocksStatsByHeight,
};

27
app/api/rpcApi.js

@ -68,8 +68,30 @@ function getNetworkHashrate(blockCount=144) {
function getBlockStats(hash) {
if (semver.gte(global.btcNodeSemver, "0.17.0")) {
return getRpcDataWithParams({method:"getblockstats", parameters:[hash]});
if (hash == coinConfig.genesisBlockHashesByNetwork[global.activeBlockchain] && coinConfig.genesisBlockStatsByNetwork[global.activeBlockchain]) {
return new Promise(function(resolve, reject) {
resolve(coinConfig.genesisBlockStatsByNetwork[global.activeBlockchain]);
});
} else {
return getRpcDataWithParams({method:"getblockstats", parameters:[hash]});
}
} else {
// unsupported
return nullPromise();
}
}
function getBlockStatsByHeight(height) {
if (semver.gte(global.btcNodeSemver, "0.17.0")) {
if (height == 0 && coinConfig.genesisBlockStatsByNetwork[global.activeBlockchain]) {
return new Promise(function(resolve, reject) {
resolve(coinConfig.genesisBlockStatsByNetwork[global.activeBlockchain]);
});
} else {
return getRpcDataWithParams({method:"getblockstats", parameters:[height]});
}
} else {
// unsupported
return nullPromise();
@ -377,5 +399,6 @@ module.exports = {
getSmartFeeEstimate: getSmartFeeEstimate,
getUtxoSetSummary: getUtxoSetSummary,
getNetworkHashrate: getNetworkHashrate,
getBlockStats: getBlockStats
getBlockStats: getBlockStats,
getBlockStatsByHeight: getBlockStatsByHeight,
};

39
app/coins/btc.js

@ -180,6 +180,45 @@ module.exports = {
"blocktime": 1296688602
}
},
genesisBlockStatsByNetwork:{
"main": {
"avgfee": 0,
"avgfeerate": 0,
"avgtxsize": 0,
"blockhash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
"feerate_percentiles": [
0,
0,
0,
0,
0
],
"height": 0,
"ins": 0,
"maxfee": 0,
"maxfeerate": 0,
"maxtxsize": 0,
"medianfee": 0,
"mediantime": 1231006505,
"mediantxsize": 0,
"minfee": 0,
"minfeerate": 0,
"mintxsize": 0,
"outs": 1,
"subsidy": 5000000000,
"swtotal_size": 0,
"swtotal_weight": 0,
"swtxs": 0,
"time": 1231006505,
"total_out": 0,
"total_size": 0,
"total_weight": 0,
"totalfee": 0,
"txs": 1,
"utxo_increase": 1,
"utxo_size_inc": 117
}
},
genesisCoinbaseOutputAddressScripthash:"8b01df4e368ea28f8dc0423bcf7a4923e3a12d307c875e47a0cfbf90b5c39161",
historicalData: [
{

4
app/utils.js

@ -322,8 +322,8 @@ function shortenTimeDiff(str) {
str = str.replace(" years", "y");
str = str.replace(" year", "y");
str = str.replace(" months", "m");
str = str.replace(" month", "m");
str = str.replace(" months", "mo");
str = str.replace(" month", "mo");
str = str.replace(" weeks", "w");
str = str.replace(" week", "w");

8
public/css/styling.css

@ -219,20 +219,20 @@ strong {
}
.summary-table-label {
max-width: 11%;
max-width: 13%;
text-align: right;
}
.summary-table-content {
max-width: 89%;
max-width: 87%;
margin-bottom: 5px;
}
.summary-split-table-label {
max-width: 22%;
max-width: 24%;
text-align: right;
}
.summary-split-table-content {
max-width: 78%;
max-width: 76%;
margin-bottom: 5px;
}

BIN
public/img/block-fullness-0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

BIN
public/img/block-fullness-1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

BIN
public/img/block-fullness-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

BIN
public/img/block-fullness-3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

83
routes/baseActionsRouter.js

@ -43,6 +43,10 @@ router.get("/", function(req, res, next) {
}
res.locals.homepage = true;
// don't need timestamp on homepage "blocks-list", this flag disables
res.locals.hideTimestampColumn = true;
// variables used by blocks-list.pug
res.locals.offset = 0;
@ -71,10 +75,25 @@ router.get("/", function(req, res, next) {
coreApi.getBlockchainInfo().then(function(getblockchaininfo) {
res.locals.getblockchaininfo = getblockchaininfo;
var blockHeights = [];
if (getblockchaininfo.blocks) {
// +1 to page size here so we have the next block to calculate T.T.M.
for (var i = 0; i < (config.site.homepage.recentBlocksCount + 1); i++) {
blockHeights.push(getblockchaininfo.blocks - i);
}
} else if (global.activeBlockchain == "regtest") {
// hack: default regtest node returns getblockchaininfo.blocks=0, despite having a genesis block
// hack this to display the genesis block
blockHeights.push(0);
}
// promiseResults[5]
promises.push(coreApi.getBlocksStatsByHeight(blockHeights));
if (getblockchaininfo.chain !== 'regtest') {
var targetBlocksPerDay = 24 * 60 * 60 / global.coinConfig.targetBlockTimeSeconds;
// promiseResults[4] (if not regtest)
// promiseResults[6] (if not regtest)
promises.push(coreApi.getTxCountStats(targetBlocksPerDay / 4, -targetBlocksPerDay, "latest"));
var chainTxStatsIntervals = [ targetBlocksPerDay, targetBlocksPerDay * 7, targetBlocksPerDay * 30, targetBlocksPerDay * 365 ]
@ -84,23 +103,12 @@ router.get("/", function(req, res, next) {
.slice(0, chainTxStatsIntervals.length)
.concat("All time");
// promiseResults[7-X] (if not regtest)
for (var i = 0; i < chainTxStatsIntervals.length; i++) {
promises.push(coreApi.getChainTxStats(chainTxStatsIntervals[i]));
}
}
var blockHeights = [];
if (getblockchaininfo.blocks) {
// +1 to page size here so we have the next block to calculate T.T.M.
for (var i = 0; i < (config.site.homepage.recentBlocksCount + 1); i++) {
blockHeights.push(getblockchaininfo.blocks - i);
}
} else if (global.activeBlockchain == "regtest") {
// hack: default regtest node returns getblockchaininfo.blocks=0, despite having a genesis block
// hack this to display the genesis block
blockHeights.push(0);
}
if (getblockchaininfo.chain !== 'regtest') {
promises.push(coreApi.getChainTxStats(getblockchaininfo.blocks - 1));
}
@ -113,6 +121,7 @@ router.get("/", function(req, res, next) {
res.locals.miningInfo = promiseResults[1];
var rawSmartFeeEsimates = promiseResults[2];
var smartFeeEsimates = {};
for (var i = 0; i < feeConfTargets.length; i++) {
@ -125,12 +134,25 @@ router.get("/", function(req, res, next) {
res.locals.hashrate1d = promiseResults[3];
res.locals.hashrate7d = promiseResults[4];
var rawblockstats = promiseResults[5];
if (rawblockstats && rawblockstats.length > 0 && rawblockstats[0] != null) {
res.locals.blockstatsByHeight = {};
for (var i = 0; i < rawblockstats.length; i++) {
var blockstats = rawblockstats[i];
res.locals.blockstatsByHeight[blockstats.height] = blockstats;
}
}
if (getblockchaininfo.chain !== 'regtest') {
res.locals.txStats = promiseResults[5];
res.locals.txStats = promiseResults[6];
var chainTxStats = [];
for (var i = 0; i < res.locals.chainTxStatsLabels.length; i++) {
chainTxStats.push(promiseResults[i + 6]);
chainTxStats.push(promiseResults[i + 7]);
}
res.locals.chainTxStats = chainTxStats;
@ -355,14 +377,39 @@ router.get("/blocks", function(req, res, next) {
}
}
}
coreApi.getBlocksByHeight(blockHeights).then(function(blocks) {
res.locals.blocks = blocks;
var promises = [];
promises.push(coreApi.getBlocksByHeight(blockHeights));
promises.push(coreApi.getBlocksStatsByHeight(blockHeights));
Promise.all(promises).then(function(promiseResults) {
res.locals.blocks = promiseResults[0];
var rawblockstats = promiseResults[1];
if (rawblockstats != null && rawblockstats.length > 0 && rawblockstats[0] != null) {
res.locals.blockstatsByHeight = {};
for (var i = 0; i < rawblockstats.length; i++) {
var blockstats = rawblockstats[i];
res.locals.blockstatsByHeight[blockstats.height] = blockstats;
}
}
res.render("blocks");
next();
}).catch(function(err) {
res.locals.pageErrors.push(utils.logError("32974hrbfbvc", err));
res.render("blocks");
next();
});
}).catch(function(err) {
res.locals.userMessage = "Error: " + err;

4
views/address.pug

@ -232,7 +232,7 @@ block content
div.card-body
div.clearfix
div.float-left
h3.h6
h3.h6.mb-0
if (addressDetails && addressDetails.txCount)
if (addressDetails.txCount == 1)
span 1 Transaction
@ -354,7 +354,7 @@ block content
- var timeAgoTime = tx.time;
small.text-muted (
include includes/time-ago-text.pug
span )
span ago)
else
span.text-danger Unconfirmed

208
views/includes/block-content.pug

@ -10,7 +10,8 @@ div.mb-2
if (result.getblock.nextblockhash)
a.btn.btn-sm.btn-primary.mb-1(href=("/block/" + result.getblock.nextblockhash)) Next Block:
span.text-monospace ##{(result.getblock.height + 1).toLocaleString()} &raquo;
span.text-monospace ##{(result.getblock.height + 1).toLocaleString()}
span &raquo;
else
a.btn.btn-sm.btn-secondary.disabled.mb-1 Next Block: N/A
small (latest block)
@ -70,7 +71,7 @@ div.tab-content
- var timeAgoTime = result.getblock.time;
span.text-muted.ml-2 (
include ./time-ago-text.pug
span )
span ago)
if (result.blockstats)
div.row
@ -78,8 +79,11 @@ div.tab-content
span.border-dotted(title="Total value of all transaction outputs (excluding the coinbase transaction)", data-toggle="tooltip")
span Total Output
div.text-monospace(class=sumTableValueClass)
- var currencyValue = new Decimal(result.blockstats.total_out).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
if (result.blockstats.total_out)
- var currencyValue = new Decimal(result.blockstats.total_out).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
else
span 0
if (result.blockstats)
div.row
@ -89,9 +93,17 @@ div.tab-content
span.text-muted.font-weight-normal.mx-1 /
span Out #
div.text-monospace(class=sumTableValueClass)
span #{result.blockstats.ins.toLocaleString()}
if (result.blockstats.ins)
span #{result.blockstats.ins.toLocaleString()}
else
span 0
span.text-muted.mx-1 /
span #{result.blockstats.outs.toLocaleString()}
if (result.blockstats.outs)
span #{result.blockstats.outs.toLocaleString()}
else
span 1
if (result.blockstats)
div.row
@ -102,16 +114,30 @@ div.tab-content
- var sizePlusMinus = (result.blockstats.utxo_size_inc > 0) ? "+" : "-";
- var sizeDeltaData = utils.formatLargeNumber(Math.abs(result.blockstats.utxo_size_inc), 1);
- var plusMinus = (result.blockstats.utxo_increase > 0) ? "+" : "";
span #{plusMinus}#{result.blockstats.utxo_increase.toLocaleString()}
span.text-muted (#{sizePlusMinus}#{sizeDeltaData[0]} #{sizeDeltaData[1].abbreviation}B)
if (result.blockstats.utxo_increase)
span #{plusMinus}#{result.blockstats.utxo_increase.toLocaleString()}
span.text-muted (#{sizePlusMinus}#{sizeDeltaData[0]}
small #{sizeDeltaData[1].abbreviation}B
span )
else
span 0
if (result.blockstats)
div.row
div(class=sumTableLabelClass) Min - Max Tx Size
div(class=sumTableLabelClass) Min, Max Tx Size
div.text-monospace(class=sumTableValueClass)
span #{result.blockstats.mintxsize.toLocaleString()}
if (result.blockstats.mintxsize)
span #{result.blockstats.mintxsize.toLocaleString()}
else
span 0
span.text-muted.mx-1 -
span #{result.blockstats.maxtxsize.toLocaleString()} B
if (result.blockstats.maxtxsize)
span #{result.blockstats.maxtxsize.toLocaleString()}
small B
else
span 0
- var blockRewardMax = coinConfig.blockRewardFunction(result.getblock.height, global.activeBlockchain);
- var coinbaseTxTotalOutputValue = new Decimal(0);
@ -121,7 +147,7 @@ div.tab-content
if (parseFloat(coinbaseTxTotalOutputValue) < blockRewardMax)
div.row
div(class=sumTableLabelClass)
span.border-dotted(data-toggle="tooltip" title="The miner of this block failed to collect this value. As a result, it is lost.") Fees Destroyed
span.border-dotted(data-toggle="tooltip" title="The miner of this block failed to collect this value. As a result, it is permanently lost.") Fees Destroyed
div.text-monospace.text-danger(class=sumTableValueClass)
- var currencyValue = new Decimal(blockRewardMax).minus(coinbaseTxTotalOutputValue);
include ./value-display.pug
@ -130,14 +156,16 @@ div.tab-content
div.row
div(class=sumTableLabelClass) Weight
div.text-monospace(class=sumTableValueClass)
span(style="") #{result.getblock.weight.toLocaleString()} wu
span #{result.getblock.weight.toLocaleString()}
small wu
span.text-muted (#{new Decimal(100 * result.getblock.weight / coinConfig.maxBlockWeight).toDecimalPlaces(2)}% full)
div.row
div(class=sumTableLabelClass) Size
div.text-monospace(class=sumTableValueClass) #{result.getblock.size.toLocaleString()} bytes
div.text-monospace(class=sumTableValueClass) #{result.getblock.size.toLocaleString()}
small B
div.row
div(class=sumTableLabelClass) Confirmations
@ -159,70 +187,104 @@ div.tab-content
hr
div.clearfix
div.row
div.summary-split-table-label Total
div.summary-split-table-content.text-monospace
- var currencyValue = new Decimal(result.getblock.totalFees);
include ./value-display.pug
if (result.blockstats.ins == 0 || !result.blockstats.ins)
span.text-monospace N/A (no inputs)
if (result.blockstats)
div.row
div.summary-split-table-label Percentiles
br
small.font-weight-normal (sat/vB)
div.summary-split-table-content.text-monospace
div.clearfix
each item, itemIndex in [10, 25, 50, 75, 90]
div.float-left.mr-3
span.font-weight-bold #{item}%
br
span #{JSON.stringify(result.blockstats.feerate_percentiles[itemIndex])}
if (result.getblock.totalFees > 0)
else
div.row
div.summary-split-table-label Average Rate
div.summary-split-table-label Total
div.summary-split-table-content.text-monospace
- var currencyValue = new Decimal(result.getblock.totalFees).dividedBy(result.getblock.strippedsize).times(coinConfig.baseCurrencyUnit.multiplier).toDecimalPlaces(1);
span #{currencyValue} sat/vB
br
span.text-muted (
- var currencyValue = new Decimal(result.getblock.totalFees).dividedBy(result.getblock.tx.length);
if (result.blockstats.totalfee)
- var currencyValue = new Decimal(result.blockstats.totalfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
span )
if (result.blockstats)
div.row
div.summary-split-table-label Median Rate
div.summary-split-table-content.text-monospace
- var currencyValue = new Decimal(result.blockstats.medianfee).dividedBy(1000).toDecimalPlaces(1);
span #{currencyValue} sat/vB
if (result.blockstats)
div.row
div.summary-split-table-label Min, Max Rate
div.summary-split-table-content.text-monospace
- var currencyValue = new Decimal(result.blockstats.minfeerate).toDecimalPlaces(1);
span #{currencyValue}
span.text-muted.mx-1 -
- var currencyValue = new Decimal(result.blockstats.maxfeerate).toDecimalPlaces(1);
span #{currencyValue}
small.ml-1 sat/vB
if (result.blockstats)
div.row
div.summary-split-table-label
span.border-dotted(title="These are the min and max fees for individual transactions included in this block.", data-toggle="tooltip") Min, Max Fee
div.summary-split-table-content.text-monospace
- var currencyValue = new Decimal(result.blockstats.minfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
br
else
- var currencyValue = new Decimal(result.getblock.totalFees);
include ./value-display.pug
- var currencyValue = new Decimal(result.blockstats.maxfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
if (result.blockstats)
div.row
div.summary-split-table-label Percentiles
br
small.font-weight-normal (sat/vB)
div.summary-split-table-content.text-monospace
div.clearfix
each item, itemIndex in [10, 25, 50, 75, 90]
div.float-left.mr-3
span.font-weight-bold #{item}%
br
if (result.blockstats.feerate_percentiles)
span #{JSON.stringify(result.blockstats.feerate_percentiles[itemIndex])}
else
span -
if (result.getblock.totalFees > 0)
div.row
div.summary-split-table-label Average Rate
div.summary-split-table-content.text-monospace
if (result.blockstats)
span #{result.blockstats.avgfeerate}
else
- var currencyValue = new Decimal(result.getblock.totalFees).dividedBy(result.getblock.strippedsize).times(coinConfig.baseCurrencyUnit.multiplier).toDecimalPlaces(1);
span #{currencyValue}
small.ml-1 sat/vB
br
span.text-muted (
- var currencyValue = new Decimal(result.getblock.totalFees).dividedBy(result.getblock.tx.length);
include ./value-display.pug
span )
if (result.blockstats)
div.row
div.summary-split-table-label Median Rate
div.summary-split-table-content.text-monospace
if (result.blockstats.medianfee)
- var currencyValue = new Decimal(result.blockstats.medianfee).dividedBy(1000).toDecimalPlaces(1);
span #{currencyValue}
small sat/vB
else
span 0
if (result.blockstats)
div.row
div.summary-split-table-label Min, Max Rate
div.summary-split-table-content.text-monospace
if (result.blockstats.minfeerate)
span #{result.blockstats.minfeerate.toLocaleString()}
else
span 0
span.text-muted.mx-1 -
if (result.blockstats.maxfeerate)
span #{result.blockstats.maxfeerate.toLocaleString()}
else
span 0
small.ml-1 sat/vB
if (result.blockstats)
div.row
div.summary-split-table-label
span.border-dotted(title="These are the min and max fees for individual transactions included in this block.", data-toggle="tooltip") Min, Max Fee
div.summary-split-table-content.text-monospace
if (result.blockstats.minfee)
- var currencyValue = new Decimal(result.blockstats.minfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
else
span 0
br
if (result.blockstats.maxfee)
- var currencyValue = new Decimal(result.blockstats.maxfee).dividedBy(coinConfig.baseCurrencyUnit.multiplier);
include ./value-display.pug
else
span 0
div.card.shadow-sm.mb-3
div.card-body.px-2.px-md-3
@ -276,7 +338,7 @@ div.tab-content
span #{chainworkData[0]}
span x 10
sup #{chainworkData[1].exponent}
span hashes
small hashes
span.text-muted (#{result.getblock.chainwork.replace(/^0+/, '')})

91
views/includes/blocks-list.pug

@ -5,22 +5,31 @@ div.table-responsive
//th
th
th.data-header.text-right Height
th.data-header.text-right Date
small (utc)
if (!hideTimestampColumn)
th.data-header.text-right
span.border-dotted(title="UTC timestamp of the block.", data-toggle="tooltip") Date
th.data-header.text-right Age
th.data-header.text-right
span.border-dotted(title="Time To Mine - The time it took to mine this block after the previous block. 'Fast' blocks (mined in < 5min) are shown in green; 'slow' blocks (mined in > 15min) are shown in red.", data-toggle="tooltip") T.T.M.
th.data-header.text-right Miner
th.data-header.text-right Transactions
th.data-header.text-right Avg Fee
small (sat/vB)
th.data-header.text-right Total Fees
th.data-header.text-right
span.border-dotted(title="The number of transactions included in each block.", data-toggle="tooltip") N(tx)
if (blockstatsByHeight)
th.data-header.text-right
span.border-dotted(title="The total output of all transactions in each block (excluding coinbase transactions).", data-toggle="tooltip") Volume
th.data-header.text-right
span.border-dotted(title="The average fee (sat/vB) for all block transactions.", data-toggle="tooltip") Avg Fee
th.data-header.text-right Σ Fees
//th.data-header.text-right Size (kB)
if (blocks && blocks.length > 0 && blocks[0].weight)
th.data-header.text-right Weight
small (kWu)
th.data-header.text-right % Full
//span.border-dotted(title="Block weight, in kWu.", data-toggle="tooltip") Weight
else
th.data-header.text-right Size
@ -56,16 +65,20 @@ div.table-responsive
if (blockIndex < blocks.length - 1)
- var timeDiff = moment.duration(moment.utc(new Date(parseInt(block.time) * 1000)).diff(moment.utc(new Date(parseInt(blocks[blockIndex + 1].time) * 1000))));
td.data-cell.text-monospace.text-right
- var timestampHuman = block.time;
include timestamp-human.pug
if (!hideTimestampColumn)
td.data-cell.text-monospace.text-right
- var timestampHuman = block.time;
include timestamp-human.pug
td.data-cell.text-monospace.text-right
if (sort != "asc" && blockIndex == 0 && offset == 0 && timeAgoTime > (15 * 60 * 1000))
span.text-danger.border-dotted(title="It's been > 15 min since this latest block.", data-toggle="tooltip") #{utils.shortenTimeDiff(timeAgo.format())}
- var timeAgoTime = block.time;
span.text-danger.border-dotted(title="It's been > 15 min since this latest block.", data-toggle="tooltip")
include ./time-ago-text.pug
else
span #{utils.shortenTimeDiff(timeAgo.format())}
- var timeAgoTime = block.time;
include ./time-ago-text.pug
td.data-cell.text-monospace.text-right
if (timeDiff)
@ -75,7 +88,14 @@ div.table-responsive
if (timeDiff > 900000)
- var colorClass = "text-danger";
span.font-weight-light(class=colorClass) #{utils.shortenTimeDiff(timeDiff.format())}
span.font-weight-light(class=colorClass)
span #{timeDiff.format()}
if (false)
if (timeDiff.asMinutes() < 1)
span #{parseInt(timeDiff.asSeconds())}s
else
span #{parseInt(timeDiff.asMinutes())}m #{parseInt(timeDiff.asSeconds())}s
else
if (block.height == 0)
@ -92,6 +112,16 @@ div.table-responsive
td.data-cell.text-monospace.text-right #{block.tx.length.toLocaleString()}
if (blockstatsByHeight)
td.data-cell.text-monospace.text-right
if (blockstatsByHeight[block.height])
- var currencyValue = parseInt(new Decimal(blockstatsByHeight[block.height].total_out).dividedBy(coinConfig.baseCurrencyUnit.multiplier));
- var currencyValueDecimals = 0;
include ./value-display.pug
else
span 0
td.data-cell.text-monospace.text-right
- var currencyValue = new Decimal(block.totalFees).dividedBy(block.strippedsize).times(coinConfig.baseCurrencyUnit.multiplier).toDecimalPlaces(1);
span #{currencyValue}
@ -115,18 +145,33 @@ div.table-responsive
if (blocks && blocks.length > 0 && blocks[0].weight)
td.data-cell.text-monospace.text-right
- var bWeightK = parseInt(block.weight / 1000);
- var fullPercent = new Decimal(100 * block.weight / coinConfig.maxBlockWeight).toDecimalPlaces(1);
if (true)
- var full = new Decimal(block.weight).dividedBy(coinConfig.maxBlockWeight).times(100);
- var full2 = full.toDP(2);
span #{full2}
if (full > 90)
img(src="/img/block-fullness-3.png", style="width: 18px; height: 18px;")
else if (full > 50)
img(src="/img/block-fullness-2.png", style="width: 18px; height: 18px;")
else if (full > 25)
img(src="/img/block-fullness-1.png", style="width: 18px; height: 18px;")
else
img(src="/img/block-fullness-0.png", style="width: 18px; height: 18px;")
span #{bWeightK.toLocaleString()}
small.font-weight-light.text-muted (#{fullPercent}%)
else
- var bWeightK = parseInt(block.weight / 1000);
- var fullPercent = new Decimal(100 * block.weight / coinConfig.maxBlockWeight).toDecimalPlaces(1);
- var bSizeK = parseInt(block.size / 1000);
span.ml-1(data-toggle="tooltip", title=`Size: ${bSizeK.toLocaleString()} kB`)
i.fas.fa-ellipsis-h.text-muted
span #{bWeightK.toLocaleString()}
small.font-weight-light.text-muted (#{fullPercent}%)
div(class="progress", style="height: 4px;")
div(class="progress-bar", role="progressbar", style=("width: " + fullPercent + "%;"), aria-valuenow=parseInt(100 * block.weight / coinConfig.maxBlockWeight), aria-valuemin="0" ,aria-valuemax="100")
- var bSizeK = parseInt(block.size / 1000);
span.ml-1(data-toggle="tooltip", title=`Size: ${bSizeK.toLocaleString()} kB`)
i.fas.fa-ellipsis-h.text-muted
div(class="progress", style="height: 4px;")
div(class="progress-bar", role="progressbar", style=("width: " + fullPercent + "%;"), aria-valuenow=parseInt(100 * block.weight / coinConfig.maxBlockWeight), aria-valuemin="0" ,aria-valuemax="100")
else
td.data-cell.text-monospace.text-right

5
views/includes/debug-overrides.pug

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

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

@ -150,52 +150,65 @@ div.row.index-summary
else
small.text-muted.border-dotted(title=utxoCalculatingDesc, data-toggle="tooltip") calculating...
if (exchangeRates)
if (networkVolume || exchangeRates)
div(class=colClass)
h5.h6 Financials
table.table.table-borderless.table-sm.table-hover
tbody
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-money-bill-wave-alt.mr-1.summary-icon
span.border-dotted(data-toggle="tooltip", title=("Exchange-rate data from: " + coinConfig.exchangeRateData.jsonUrl)) Exchange Rate
td.text-right.text-monospace
span #{utils.formatValueInActiveCurrency(1.0)}
tr
th.px-2.px-lg-0.px-xl-2
i.fab.fa-btc.mr-1.summary-icon
span Sats Rate
td.text-right.text-monospace
- var satsRateData = utils.satoshisPerUnitOfActiveCurrency();
span #{satsRateData.amt}
small #{satsRateData.unit}
if (utxoSetSummary || utxoSetSummaryPending)
if (exchangeRates)
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-globe.mr-1.summary-icon
span Market Cap
i.fas.fa-money-bill-wave-alt.mr-1.summary-icon
span.border-dotted(data-toggle="tooltip", title=("Exchange-rate data from: " + coinConfig.exchangeRateData.jsonUrl)) Exchange Rate
td.text-right.text-monospace
if (utxoSetSummary)
- var activeCurrency = global.currencyFormatType.length > 0 ? global.currencyFormatType : "usd";
- var xxx = utils.formatLargeNumber(parseFloat(utxoSetSummary.total_amount) * exchangeRates[activeCurrency], 1);
span #{utils.formatValueInActiveCurrency(1.0)}
if (activeCurrency == "eur")
span €
else
span $
if (exchangeRates)
tr
th.px-2.px-lg-0.px-xl-2
i.fab.fa-btc.mr-1.summary-icon
span Sats Rate
td.text-right.text-monospace
- var satsRateData = utils.satoshisPerUnitOfActiveCurrency();
span #{satsRateData.amt}
small #{satsRateData.unit}
if (exchangeRates)
if (utxoSetSummary || utxoSetSummaryPending)
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-globe.mr-1.summary-icon
span Market Cap
td.text-right.text-monospace
if (utxoSetSummary)
- var activeCurrency = global.currencyFormatType.length > 0 ? global.currencyFormatType : "usd";
- var xxx = utils.formatLargeNumber(parseFloat(utxoSetSummary.total_amount) * exchangeRates[activeCurrency], 1);
if (activeCurrency == "eur")
span €
else
span $
span #{xxx[0]}
if (xxx[1].textDesc)
span #{xxx[1].textDesc}
else
span x 10
sup #{xxx[1].exponent}
// ["154.9",{"val":1000000000,"name":"giga","abbreviation":"G","exponent":"9"}]
span #{xxx[0]}
if (xxx[1].textDesc)
span #{xxx[1].textDesc}
else
span x 10
sup #{xxx[1].exponent}
// ["154.9",{"val":1000000000,"name":"giga","abbreviation":"G","exponent":"9"}]
small.text-muted.border-dotted(title=utxoCalculatingDesc, data-toggle="tooltip") calculating...
else
small.text-muted.border-dotted(title=utxoCalculatingDesc, data-toggle="tooltip") calculating...
if (networkVolume)
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-history.mr-1.summary-icon
span.border-dotted(title=`Total output of all transactions (excluding coinbase transactions) over the last 24 hrs (blocks: [#${networkVolume.d1.endBlock.toLocaleString()} - #${networkVolume.d1.startBlock.toLocaleString()}]).`, data-toggle="tooltip") Volume
small.ml-1 (24h)
td.text-right.text-monospace
- var currencyValue = parseInt(networkVolume.d1.amt);
- var currencyValueDecimals = 0;
include ./value-display.pug

16
views/includes/time-ago-text.pug

@ -1,3 +1,17 @@
- var timeAgo = moment.duration(moment.utc(new Date()).diff(moment.utc(new Date(parseInt(timeAgoTime) * 1000))));
span #{utils.shortenTimeDiff(timeAgo.format())} ago
if (timeAgo.asHours() < 1)
if (timeAgo.asMinutes() < 1)
span <1m
else
span #{timeAgo.minutes()}m
else
if (timeAgo.asHours() >= 1 && timeAgo.asHours() < 24)
span #{timeAgo.hours()}h
if (timeAgo.minutes() > 0)
span #{timeAgo.minutes()}m
else
span #{utils.shortenTimeDiff(timeAgo.format())}

12
views/includes/timestamp-human.pug

@ -1,10 +1,14 @@
span #{moment.utc(new Date(parseInt(timestampHuman) * 1000)).format("M/D")}
- var yearStr = moment.utc(new Date(parseInt(timestampHuman) * 1000)).format("Y");
- var nowYearStr = moment.utc(new Date()).format("Y");
if (yearStr != nowYearStr)
span , #{yearStr}
- var dateStr = moment.utc(new Date(parseInt(timestampHuman) * 1000)).format("Y-M-D");
- var nowDateStr = moment.utc(new Date()).format("Y-M-D");
if (dateStr == nowDateStr)
else
span #{moment.utc(new Date(parseInt(timestampHuman) * 1000)).format("M/D")}
if (yearStr != nowYearStr)
span , #{yearStr}
span
span.border-dotted(title=`${moment.utc(new Date(parseInt(timestampHuman) * 1000)).format("Y-MM-DD HH:mm:ss")}`, data-toggle="tooltip") #{moment.utc(new Date(parseInt(timestampHuman) * 1000)).format("HH:mm")}

4
views/includes/transaction-io-details.pug

@ -165,6 +165,8 @@ div.row.text-monospace
span(title="Output Type: Witness, v0 Key Hash", data-toggle="tooltip") v0_p2wpkh
else if (vout.scriptPubKey.type == "witness_v0_scripthash")
span(title="Output Type: Witness, v0 Script Hash", data-toggle="tooltip") v0_p2wsh
else if (vout.scriptPubKey.type == "nonstandard")
span(title="Output Type: Non-Standard", data-toggle="tooltip") nonstandard
else
span ???
@ -208,6 +210,8 @@ div.row.text-monospace
span(title="Output Type: Witness, v0 Key Hash", data-toggle="tooltip") v0_p2wpkh
else if (vout.scriptPubKey.type == "witness_v0_scripthash")
span(title="Output Type: Witness, v0 Script Hash", data-toggle="tooltip") v0_p2wsh
else if (vout.scriptPubKey.type == "nonstandard")
span(title="Output Type: Non-Standard", data-toggle="tooltip") nonstandard
else
span ???

29
views/transaction.pug

@ -116,12 +116,10 @@ block content
include includes/timestamp-human.pug
small.ml-1 utc
br
- var timeAgoTime = result.getrawtransaction.time;
span.text-muted (
span.text-muted.ml-2 (
include includes/time-ago-text.pug
span )
span ago)
div.row
div.summary-table-label Version
@ -132,12 +130,15 @@ block content
div.summary-table-content.text-monospace
if (result.getrawtransaction.vsize != result.getrawtransaction.size)
span #{result.getrawtransaction.vsize.toLocaleString()}
span.border-dotted(title="Virtual bytes", data-toggle="tooltip") vB
small vB
br
span.text-muted (#{result.getrawtransaction.size.toLocaleString()} B)
span.text-muted (#{result.getrawtransaction.size.toLocaleString()}
small B
span )
else
span #{result.getrawtransaction.size.toLocaleString()} B
span #{result.getrawtransaction.size.toLocaleString()}
small B
if (result.getrawtransaction.locktime > 0)
div.row
@ -179,7 +180,7 @@ block content
if (parseFloat(totalOutputValue) < parseFloat(blockRewardMax))
div.row
div.summary-table-label
span.border-dotted(data-toggle="tooltip" title="The miner of this transaction's block failed to collect this value. As a result, it is lost.") Fees Destroyed
span.border-dotted(data-toggle="tooltip" title="The miner of this transaction's block failed to collect this value. As a result, it is permanently lost.") Fees Destroyed
div.summary-table-content.text-monospace.text-danger
- var currencyValue = new Decimal(blockRewardMax).minus(totalOutputValue);
include includes/value-display.pug
@ -220,11 +221,17 @@ block content
div.summary-table-label Fee Rate
div.summary-table-content.text-monospace
if (result.getrawtransaction.vsize != result.getrawtransaction.size)
span #{utils.addThousandsSeparators(new DecimalRounded(totalInputValue).minus(totalOutputValue).dividedBy(result.getrawtransaction.vsize).times(100000000))} sat/
span.border-dotted(title="Virtual bytes" data-toggle="tooltip") vB
span #{utils.addThousandsSeparators(new DecimalRounded(totalInputValue).minus(totalOutputValue).dividedBy(result.getrawtransaction.vsize).times(100000000))}
small sat/vB
br
span.text-muted (#{utils.addThousandsSeparators(new DecimalRounded(totalInputValue).minus(totalOutputValue).dividedBy(result.getrawtransaction.size).times(100000000))} sat/B)
span.text-muted (#{utils.addThousandsSeparators(new DecimalRounded(totalInputValue).minus(totalOutputValue).dividedBy(result.getrawtransaction.size).times(100000000))}
small sat/B
span )
else
span #{utils.addThousandsSeparators(new DecimalRounded(totalInputValue).minus(totalOutputValue).dividedBy(result.getrawtransaction.size).times(100000000))}
small sat/B
if (result.getrawtransaction.vin[0].coinbase)

Loading…
Cancel
Save