Browse Source

More, more, more:

- new tool /difficulty-history: show graph of diff changes over time for fun
- new "Network Summary" item: Block time for current difficulty epoch, with estimate of difficulty change for next epoch
- more resilient layouts for tools card on homepage and /tools page
- remove some unused code
master
Dan Janosik 5 years ago
parent
commit
a01957c47d
No known key found for this signature in database GPG Key ID: C6F8CE9FFDB2CED2
  1. 2
      CHANGELOG.md
  2. 203
      app/api/coreApi.js
  3. 21
      app/api/rpcApi.js
  4. 1
      app/coins/btc.js
  5. 3
      app/config.js
  6. 19
      app/utils.js
  7. 15
      routes/apiRouter.js
  8. 40
      routes/baseActionsRouter.js
  9. 202
      views/difficulty-history.pug
  10. 26
      views/includes/index-network-summary.pug
  11. 79
      views/includes/tools-card.pug
  12. 78
      views/tools.pug

2
CHANGELOG.md

@ -10,6 +10,7 @@
* Total coins in circulation
* Market cap
* 24-hour network volume (sum of tx outputs). This value is calculated at app launch and refreshed every 30min.
* Avg block time for current difficulty epoch with estimate of next difficulty adjustment
* 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)
@ -37,6 +38,7 @@
* New tool `/mining-summary` for viewing summarized mining data from recent blocks
* New tool `/block-analysis` for analyzing the details of transactions in a block.
* **IMPORTANT**: Use of `/block-analysis` can put heavy memory pressure on this app, depending on the details of the block being analyzed. If your app is crashing, consider setting a higher memory ceiling: `node --max_old_space_size=XXX bin/www` (where `XXX` is measured in MB).
* New tool `/difficulty-history` showing a graph of the history of all difficulty adjustments
* Change `/mempool-summary` to load data via ajax (UX improvement to give feedback while loading large data sets)
* Zero-indexing for tx index-in-block values
* Reduced memory usage

203
app/api/coreApi.js

@ -404,183 +404,21 @@ function getMempoolDetails(start, count) {
});
}
function getMempoolStats() {
return new Promise(function(resolve, reject) {
tryCacheThenRpcApi(miscCache, "getRawMempool", 5000, rpcApi.getRawMempool).then(function(result) {
var maxFee = 0;
var maxFeePerByte = 0;
var maxAge = 0;
var maxSize = 0;
var ages = [];
var sizes = [];
for (var txid in result) {
var txMempoolInfo = result[txid];
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:txid});
sizes.push({size:size, txid:txid});
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 satoshiPerByteBucketMaxima = coins[config.coin].feeSatoshiPerByteBucketMaxima;
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 txid in result) {
var txMempoolInfo = result[txid];
var fee = txMempoolInfo.modifiedfee;
var size = txMempoolInfo.vsize ? txMempoolInfo.vsize : txMempoolInfo.size;
var feePerByte = txMempoolInfo.modifiedfee / size;
var satoshiPerByte = feePerByte * global.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"]);
}
function getBlockByHeight(blockHeight) {
return tryCacheThenRpcApi(blockCache, "getBlockByHeight-" + blockHeight, 3600000, function() {
return rpcApi.getBlockByHeight(blockHeight);
});
}
/*debugLog(JSON.stringify(ageBuckets));
debugLog(JSON.stringify(ageBucketLabels));
debugLog(JSON.stringify(sizeBuckets));
debugLog(JSON.stringify(sizeBucketLabels));*/
function getBlocksByHeight(blockHeights) {
return new Promise(function(resolve, reject) {
var promises = [];
for (var i = 0; i < blockHeights.length; i++) {
promises.push(getBlockByHeight(blockHeights[i]));
}
resolve(summary);
Promise.all(promises).then(function(results) {
resolve(results);
}).catch(function(err) {
reject(err);
@ -588,17 +426,17 @@ function getMempoolStats() {
});
}
function getBlockByHeight(blockHeight) {
return tryCacheThenRpcApi(blockCache, "getBlockByHeight-" + blockHeight, 3600000, function() {
return rpcApi.getBlockByHeight(blockHeight);
function getBlockHeaderByHeight(blockHeight) {
return tryCacheThenRpcApi(blockCache, "getBlockHeaderByHeight-" + blockHeight, 3600000, function() {
return rpcApi.getBlockHeaderByHeight(blockHeight);
});
}
function getBlocksByHeight(blockHeights) {
function getBlockHeadersByHeight(blockHeights) {
return new Promise(function(resolve, reject) {
var promises = [];
for (var i = 0; i < blockHeights.length; i++) {
promises.push(getBlockByHeight(blockHeights[i]));
promises.push(getBlockHeaderByHeight(blockHeights[i]));
}
Promise.all(promises).then(function(results) {
@ -1085,7 +923,6 @@ module.exports = {
getRawTransactionsWithInputs: getRawTransactionsWithInputs,
getTxUtxos: getTxUtxos,
getMempoolTxDetails: getMempoolTxDetails,
getMempoolStats: getMempoolStats,
getUptimeSeconds: getUptimeSeconds,
getHelp: getHelp,
getRpcMethodHelp: getRpcMethodHelp,
@ -1102,5 +939,7 @@ module.exports = {
getBlockStats: getBlockStats,
getBlockStatsByHeight: getBlockStatsByHeight,
getBlocksStatsByHeight: getBlocksStatsByHeight,
buildBlockAnalysisData: buildBlockAnalysisData
buildBlockAnalysisData: buildBlockAnalysisData,
getBlockHeaderByHeight: getBlockHeaderByHeight,
getBlockHeadersByHeight: getBlockHeadersByHeight
};

21
app/api/rpcApi.js

@ -170,6 +170,25 @@ function getBlockByHeight(blockHeight) {
});
}
function getBlockHeaderByHash(blockhash) {
return getRpcDataWithParams({method:"getblockheader", parameters:[blockhash]});
}
function getBlockHeaderByHeight(blockHeight) {
return new Promise(function(resolve, reject) {
getRpcDataWithParams({method:"getblockhash", parameters:[blockHeight]}).then(function(blockhash) {
getBlockHeaderByHash(blockhash).then(function(blockHeader) {
resolve(blockHeader);
}).catch(function(err) {
reject(err);
});
}).catch(function(err) {
reject(err);
});
});
}
function getBlockByHash(blockHash) {
debugLog("getBlockByHash: %s", blockHash);
@ -439,6 +458,8 @@ module.exports = {
getNetworkHashrate: getNetworkHashrate,
getBlockStats: getBlockStats,
getBlockStatsByHeight: getBlockStatsByHeight,
getBlockHeaderByHash: getBlockHeaderByHash,
getBlockHeaderByHeight: getBlockHeaderByHeight,
minRpcVersions: minRpcVersions
};

1
app/coins/btc.js

@ -64,6 +64,7 @@ module.exports = {
],
maxBlockWeight: 4000000,
maxBlockSize: 1000000,
difficultyAdjustmentBlockCount: 2016,
maxSupply: new Decimal(20999817.31308491), // ref: https://bitcoin.stackexchange.com/a/38998
targetBlockTimeSeconds: 600,
currencyUnits:currencyUnits,

3
app/config.js

@ -166,7 +166,7 @@ module.exports = {
]
},
subHeaderToolsList:[0, 10, 9, 4, 11, 6, 7], // indexes in "siteTools" below that are shown in the site "sub menu" (visible on all pages except homepage)
prioritizedToolIdsList: [0, 10, 11, 9, 3, 4, 2, 5, 1, 6, 7, 8],
prioritizedToolIdsList: [0, 10, 11, 9, 3, 4, 2, 5, 12, 1, 6, 7, 8],
},
credentials: credentials,
@ -189,6 +189,7 @@ module.exports = {
/* 9 */ {name:"Mining Summary", url:"/mining-summary", desc:"Summary of recent data about miners.", fontawesome:"fas fa-chart-pie"},
/* 10 */ {name:"Block Stats", url:"/block-stats", desc:"Summary data for blocks in configurable range.", fontawesome:"fas fa-layer-group"},
/* 11 */ {name:"Block Analysis", url:"/block-analysis", desc:"Summary analysis for all transactions in a block.", fontawesome:"fas fa-angle-double-down"},
/* 12 */ {name:"Difficulty History", url:"/difficulty-history", desc:"Graph of difficulty changes over time.", fontawesome:"fas fa-chart-line"},
],
donations:{

19
app/utils.js

@ -98,6 +98,24 @@ function splitArrayIntoChunks(array, chunkSize) {
return chunks;
}
function splitArrayIntoChunksByChunkCount(array, chunkCount) {
var bigChunkSize = Math.ceil(array.length / chunkCount);
var bigChunkCount = chunkCount - (chunkCount * bigChunkSize - array.length);
var chunks = [];
var chunkStart = 0;
for (var chunk = 0; chunk < chunkCount; chunk++) {
var chunkSize = (chunk < bigChunkCount ? bigChunkSize : (bigChunkSize - 1));
chunks.push(array.slice(chunkStart, chunkStart + chunkSize));
chunkStart += chunkSize;
}
return chunks;
}
function getRandomString(length, chars) {
var mask = '';
@ -719,6 +737,7 @@ module.exports = {
redirectToConnectPageIfNeeded: redirectToConnectPageIfNeeded,
hex2ascii: hex2ascii,
splitArrayIntoChunks: splitArrayIntoChunks,
splitArrayIntoChunksByChunkCount: splitArrayIntoChunksByChunkCount,
getRandomString: getRandomString,
getCurrencyFormatInfo: getCurrencyFormatInfo,
formatCurrencyAmount: formatCurrencyAmount,

15
routes/apiRouter.js

@ -41,6 +41,21 @@ router.get("/blocks-by-height/:blockHeights", function(req, res, next) {
});
});
router.get("/block-headers-by-height/:blockHeights", function(req, res, next) {
var blockHeightStrs = req.params.blockHeights.split(",");
var blockHeights = [];
for (var i = 0; i < blockHeightStrs.length; i++) {
blockHeights.push(parseInt(blockHeightStrs[i]));
}
coreApi.getBlockHeadersByHeight(blockHeights).then(function(result) {
res.json(result);
next();
});
});
router.get("/block-stats-by-height/:blockHeights", function(req, res, next) {
var blockHeightStrs = req.params.blockHeights.split(",");

40
routes/baseActionsRouter.js

@ -79,6 +79,9 @@ router.get("/", function(req, res, next) {
coreApi.getBlockchainInfo().then(function(getblockchaininfo) {
res.locals.getblockchaininfo = getblockchaininfo;
res.locals.difficultyPeriod = parseInt(Math.floor(getblockchaininfo.blocks / coinConfig.difficultyAdjustmentBlockCount));
var blockHeights = [];
if (getblockchaininfo.blocks) {
// +1 to page size here so we have the next block to calculate T.T.M.
@ -94,10 +97,19 @@ router.get("/", function(req, res, next) {
// promiseResults[5]
promises.push(coreApi.getBlocksStatsByHeight(blockHeights));
// promiseResults[6]
promises.push(new Promise(function(resolve, reject) {
coreApi.getBlockHeaderByHeight(coinConfig.difficultyAdjustmentBlockCount * res.locals.difficultyPeriod).then(function(difficultyPeriodFirstBlockHeader) {
console.log("abc: " + JSON.stringify(difficultyPeriodFirstBlockHeader));
resolve(difficultyPeriodFirstBlockHeader);
});
}));
if (getblockchaininfo.chain !== 'regtest') {
var targetBlocksPerDay = 24 * 60 * 60 / global.coinConfig.targetBlockTimeSeconds;
// promiseResults[6] (if not regtest)
// promiseResults[7] (if not regtest)
promises.push(coreApi.getTxCountStats(targetBlocksPerDay / 4, -targetBlocksPerDay, "latest"));
var chainTxStatsIntervals = [ targetBlocksPerDay, targetBlocksPerDay * 7, targetBlocksPerDay * 30, targetBlocksPerDay * 365 ]
@ -107,7 +119,7 @@ router.get("/", function(req, res, next) {
.slice(0, chainTxStatsIntervals.length)
.concat("All time");
// promiseResults[7-X] (if not regtest)
// promiseResults[8-X] (if not regtest)
for (var i = 0; i < chainTxStatsIntervals.length; i++) {
promises.push(coreApi.getChainTxStats(chainTxStatsIntervals[i]));
}
@ -119,6 +131,7 @@ router.get("/", function(req, res, next) {
coreApi.getBlocksByHeight(blockHeights).then(function(latestBlocks) {
res.locals.latestBlocks = latestBlocks;
res.locals.blocksUntilDifficultyAdjustment = ((res.locals.difficultyPeriod + 1) * coinConfig.difficultyAdjustmentBlockCount) - latestBlocks[0].height;
Promise.all(promises).then(function(promiseResults) {
res.locals.mempoolInfo = promiseResults[0];
@ -156,14 +169,16 @@ router.get("/", function(req, res, next) {
res.locals.blockstatsByHeight[blockstats.height] = blockstats;
}
}
res.locals.difficultyPeriodFirstBlockHeader = promiseResults[6];
if (getblockchaininfo.chain !== 'regtest') {
res.locals.txStats = promiseResults[6];
res.locals.txStats = promiseResults[7];
var chainTxStats = [];
for (var i = 0; i < res.locals.chainTxStatsLabels.length; i++) {
chainTxStats.push(promiseResults[i + 7]);
chainTxStats.push(promiseResults[i + 8]);
}
res.locals.chainTxStats = chainTxStats;
@ -1424,6 +1439,23 @@ router.get("/tx-stats", function(req, res, next) {
});
});
router.get("/difficulty-history", function(req, res, next) {
coreApi.getBlockchainInfo().then(function(getblockchaininfo) {
res.locals.blockCount = getblockchaininfo.blocks;
res.render("difficulty-history");
next();
}).catch(function(err) {
res.locals.userMessage = "Error: " + err;
res.render("difficulty-history");
next();
});
});
router.get("/about", function(req, res, next) {
res.render("about");

202
views/difficulty-history.pug

@ -0,0 +1,202 @@
extends layout
block headContent
title Difficulty History
block content
h1.h3 Difficulty History
hr
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Explanation
hr
ul.mb-0
li Mining difficulty adjusts automatically every 2,016 blocks.
li The adjustment aims to maintain an average block-mining time of 10 minutes.
li A growth in the difficulty indicates that the average block-mining time during the previous adjustment epoch was less than 10 minutes (due to more miners joining the network and 'searching' / 'mining' for blocks).
li A drop in difficulty indicates miners have left the network so finding each block is adjusted to be 'easier' for the smaller number remaining.
li The difficulty 'value' is a multiple of the difficulty of finding the easiest block (Block #0) - e.g. blocks in epoch 308 are over 16 trillion times harder to mine than those in epoch 0.
div#progress-wrapper.mb-huge
div.card.shadow-sm.mb-3
div.card-body
h4.h6 Loading data:
span(id="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#main-content(style="display: none;")
div.row
div.col
div.card.shadow-sm.mb-3
div.card-body
h3.h6.mb-0 Difficulty History
hr
canvas(id="graph-diff-hist")
block endOfBody
script(src="/js/chart.bundle.min.js", integrity="sha384-qgOtiGNaHh9fVWUnRjyHlV39rfbDcvPPkEzL1RHvsHKbuqUqM6uybNuVnghY2z4/")
script(src='/js/decimal.js')
script.
var blockCount = !{blockCount};
var heights = [];
var height = 0;
while (height <= blockCount) {
heights.push([height]);
height += !{coinConfig.difficultyAdjustmentBlockCount};
}
$(document).ready(function() {
loadData(heights, 1, heights.length);
});
function loadData(heightChunks, chunkSize, count) {
var chunkStrs = [];
for (var i = 0; i < heightChunks.length; i++) {
var heightChunk = heightChunks[i];
var chunkStr = "";
for (var j = 0; j < heightChunk.length; j++) {
if (j > 0) {
chunkStr += ",";
}
chunkStr += heightChunk[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);
$("#progress-text").text(`${Math.min(((chunkIndexDone + 1) * chunkSize), count).toLocaleString()} of ${count.toLocaleString()} (${wPercent})`);
};
var finishedCallback = function() {
var summary = summarizeData(results);
createGraph("graph-diff-hist", [summary.graphData], "Difficulty");
//createGraph("graph-diff-hist-2", summary.epochChunks, "Difficulty 2");
$("#main-content").show();
$("#progress-wrapper").hide();
};
getData(results, chunkStrs, 0, statusCallback, finishedCallback);
}
function createGraph(graphId, datas, yLabelStr) {
var datasets = [];
for (var i = 0; i < datas.length; i++) {
datasets.push({
borderColor: '#007bff',
borderWidth: 2,
backgroundColor: 'rgba(0,0,0,0)',
data: datas[i],
pointRadius: 1
});
}
var ctx = document.getElementById(graphId).getContext('2d');
var graph = new Chart(ctx, {
type: 'line',
data: {
datasets: datasets
},
options: {
legend: { display: false },
scales: {
xAxes: [{
type: 'linear',
position: 'bottom',
scaleLabel: {
display: true,
labelString: 'Difficulty Epoch'
},
//ticks: {
// stepSize: 100,
//}
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: yLabelStr
}
}]
}
}
});
}
function getData(results, chunks, chunkIndex, statusCallback, finishedCallback) {
if (chunkIndex > chunks.length - 1) {
finishedCallback();
return;
}
var url = `/api/block-headers-by-height/${chunks[chunkIndex]}`;
//console.log(url);
$.ajax({
url: url
}).done(function(result) {
for (var i = 0; i < result.length; i++) {
results.push(result[i]);
}
statusCallback(chunkIndex, chunks.length);
getData(results, chunks, chunkIndex + 1, statusCallback, finishedCallback);
});
}
function summarizeData(raw) {
var summary = {};
summary.graphData = [];
for (var i = 0; i < raw.length; i++) {
summary.graphData.push({x:i, y:raw[i].difficulty});
}
/*
var epochChunkCount = 3;
var epochChunkSize = Math.floor(summary.graphData.length / epochChunkCount);
summary.epochChunks = [];
for (var i = 0; i < epochChunkCount; i++) {
summary.epochChunks.push([]);
var scale = summary.graphData[i * epochChunkSize].y;
for (var j = 0; j < 100; j++) {
var data = summary.graphData[i * epochChunkSize + j];
summary.epochChunks[i].push({x:j, y:(data.y / scale)});
}
}*/
return summary;
}

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

@ -77,6 +77,32 @@ div.row.index-summary
span #{smartFeeEstimates[1008]}
small sat/vB
tr
th.px-2.px-lg-0.px-xl-2
i.fas.fa-clock.mr-1.summary-icon
span.border-dotted(title=`Average block time for blocks in the current difficulty epoch (#${difficultyPeriod}). The difference between the target (10min) and this time indicates how the difficulty will adjust for the next epoch.`, data-toggle="tooltip") Block Time
small.ml-1 (e#{difficultyPeriod.toLocaleString()})
td.text-right.text-monospace
- var firstBlockHeader = difficultyPeriodFirstBlockHeader;
- var currentBlock = latestBlocks[0];
- var heightDiff = currentBlock.height - firstBlockHeader.height;
- var timeDiff = currentBlock.mediantime - firstBlockHeader.mediantime;
- var timePerBlock = timeDiff / heightDiff;
- var timePerBlockDuration = moment.duration(timePerBlock * 1000);
if (timePerBlock > 600)
- var diffAdjPercent = new Decimal(timeDiff / heightDiff / 600).times(100).minus(100);
- var diffAdjText = `Prediction for next difficulty adjustment (in ${blocksUntilDifficultyAdjustment} block${blocksUntilDifficultyAdjustment == 1 ? "" : "s"}): -${diffAdjPercent.toDP(1)}%`;
- var diffAdjSign = "-";
else
- var diffAdjPercent = new Decimal(100).minus(new Decimal(timeDiff / heightDiff / 600).times(100));
- var diffAdjText = `Prediction for next difficulty adjustment (in ${blocksUntilDifficultyAdjustment} block${blocksUntilDifficultyAdjustment == 1 ? "" : "s"}): +${diffAdjPercent.toDP(1)}%`;
- var diffAdjSign = "+";
span.border-dotted(title=diffAdjText, data-toggle="tooltip") #{timePerBlockDuration.format()}
small.text-muted.ml-1 (#{diffAdjSign}#{diffAdjPercent.toDP(1)}%)
div(class=colClass)
h5.h6 Blockchain

79
views/includes/tools-card.pug

@ -46,56 +46,47 @@ if (false)
span #{siteTool.name}
if (true)
div.row
// split into 4 segments:
// xxl: 2 columns (2 col because on xxl the tools card is in the same row as the "Network Summary" card)
// md: 3 columns (requires separate layout implementation...see below)
// lg, xl: 4 columns
// xm: 2 columns
div
- var priorityList = config.site.prioritizedToolIdsList;
- var indexLists = utils.splitArrayIntoChunks(priorityList, 3);
- var indexListsMediumWidth = utils.splitArrayIntoChunks(priorityList, 4);
- var indexLists2Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 2);
- var indexLists3Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 3);
- var indexLists4Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 4);
- var indexLists5Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 5);
// special case for medium-width layout
div.col.d-none.d-md-block.d-lg-none
// xs, sm, xxl
div.d-block.d-md-none.d-xxl-block(id="tools-2-col")
div.row
div.col-md-4
ul.list-unstyled.mb-0
each toolsItemIndex in indexListsMediumWidth[0]
include tools-card-block.pug
div.col-md-4
ul.list-unstyled.mb-0
each toolsItemIndex in indexListsMediumWidth[1]
include tools-card-block.pug
div.col-md-4
ul.list-unstyled.mb-0
each toolsItemIndex in indexListsMediumWidth[2]
include tools-card-block.pug
each indexList in indexLists2Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
include tools-card-block.pug
// the below 2 div.col's are the default layout, used everywhere except medium-width layout
div.col.d-sm-block.d-md-none.d-lg-block
// md
div.d-none.d-md-block.d-lg-none(id="tools-3-col")
div.row
div.col-md-6.col-xxl-12
ul.list-unstyled.mb-0
each toolsItemIndex in indexLists[0]
include tools-card-block.pug
each indexList in indexLists3Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
include tools-card-block.pug
div.col-md-6.col-xxl-12
ul.list-unstyled.mb-0
each toolsItemIndex in indexLists[1]
include tools-card-block.pug
// lg
div.d-none.d-lg-block.d-xl-none(id="tools-4-col")
div.row
each indexList in indexLists4Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
include tools-card-block.pug
div.col.d-md-none.d-lg-block
// xl
div.d-none.d-xl-block.d-xxl-none(id="tools-5-col")
div.row
div.col-md-6.col-xxl-12
ul.list-unstyled.mb-0
each toolsItemIndex in indexLists[2]
include tools-card-block.pug
each indexList in indexLists5Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
include tools-card-block.pug
div.col-md-6.col-xxl-12
ul.list-unstyled.mb-0
each toolsItemIndex in indexLists[3]
include tools-card-block.pug

78
views/tools.pug

@ -9,19 +9,67 @@ block content
div.card.shadow-sm.mb-huge
div.card-body
div.px-2
- var priorityList = config.site.prioritizedToolIdsList;
- var indexLists1Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 1);
- var indexLists2Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 2);
- var indexLists3Col = utils.splitArrayIntoChunksByChunkCount(priorityList, 3);
// xs
div.d-block.d-sm-none(id="tools-1-col")
div.row
each indexList in indexLists1Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-left.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
div.float-left
div.lead
a(href=item.url) #{item.name}
div(style="padding-left: 39px;")
p #{item.desc}
// sm, md, lg
div.d-none.d-sm-block.d-xl-none(id="tools-2-col")
div.row
each itemIndex in config.site.prioritizedToolIdsList
div.col-md-6.col-lg-4
- var item = config.siteTools[itemIndex];
div.clearfix
div.float-left.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
div.float-left
div.lead
a(href=item.url) #{item.name}
div(style="padding-left: 39px;")
p #{item.desc}
each indexList in indexLists2Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-left.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
div.float-left
div.lead
a(href=item.url) #{item.name}
div(style="padding-left: 39px;")
p #{item.desc}
// xl, xxl
div.d-none.d-xl-block(id="tools-3-col")
div.row
each indexList in indexLists3Col
div.col
ul.list-unstyled.mb-0
each toolsItemIndex in indexList
- var item = config.siteTools[toolsItemIndex];
div.clearfix
div.float-left.pt-1(style="width: 38px;")
i.fa-lg(class=item.fontawesome, style="width: 20px; margin-right: 10px;")
div.float-left
div.lead
a(href=item.url) #{item.name}
div(style="padding-left: 39px;")
p #{item.desc}
Loading…
Cancel
Save