btc-rpc-explorer/views/difficulty-history.pug
Dan Janosik a678c8a6a9
-more styling tweaks/cleanup/consistency
-new fun item showing example of future-dated timelock (coinbase) tx
-special-case code for mainnet genesis coinbase tx, to avoid indicating that satoshi spent these unspendable coins
-tx details: skip displaying locktime when it's meaningless (the raw data is in the json if anyone wants it); show additional info about locktime meaning
-tx details: show byte-size in addition to vByte size, if applicable
-fix for recently broken donut chart on /mining-summary
-changelog
2021-12-06 16:43:10 -05:00

458 lines
13 KiB
Text

extends layout
block headContent
title Difficulty History
block content
+pageTitle("Difficulty History")
+dismissableInfoAlert("diffHistPageNoteDismissed", "About the Difficulty Adjustment...")
h6.mb-2 About the Difficulty Adjustment
ul.mb-0.ms-n3
li Mining difficulty adjusts automatically every #{coinConfig.difficultyAdjustmentBlockCount.toLocaleString()} blocks.
li The adjustment aims to maintain an average block-mining time of #{coinConfig.targetBlockTimeMinutes} minutes.
li An increase in the difficulty indicates that the average block-mining time during the previous adjustment epoch was less than #{coinConfig.targetBlockTimeMinutes} minutes (due to more miners joining the network and searching for blocks).
li A decrease in difficulty indicates miners have left the network so finding each block is adjusted to be easier for the smaller number remaining.
li The numeric Difficulty is a multiple of the difficulty of finding the easiest block (Block #0) - e.g. blocks in BTC mainnet 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
span Loading data:
span(id="progress-text")
div.progress.mt-2(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;")
- var yearItems = [["All Time", 10000], ["8 yr", 8], ["4 yr", 4], ["2 yr", 2], ["1 yr", 1]];
+pageTabs(["Details", "Data"])
.tab-content
+pageTab("Details", true)
.row
.col
+contentSection("Difficulty History")
.btn-group.mb-3
each yearItem in yearItems
a.btn.btn-outline-primary(id=`diff-hist-selector-${yearItem[1]}`, class="diff-hist-selector", href=`javascript:void(0)`, onclick=`$('.graph-diff-hist').hide(); $('#graph-diff-hist-${yearItem[1]}').show(); $(".diff-hist-selector").addClass("btn-outline-primary").removeClass("btn-primary"); $(this).toggleClass("btn-outline-primary").addClass("btn-primary"); return false;`) #{yearItem[0]}
each yearItem in yearItems
canvas(id=`graph-diff-hist-${yearItem[1]}`, class='graph-diff-hist')
.row
.col
.clearfix
.float-start
+sectionTitle("Difficulty Δ")
.float-start
small.text-muted.ms-2
| (%, clamped to ±100%)
+contentSection
canvas(id="graph-diff-change")
+pageTab("Data")
.row
.col
+contentSection("Raw Data")
.table-responsive
table.table.table-striped.table-borderless.mb-3
thead
tr
th.text-end.text-card-highlight.text-uppercase.fw-light Epoch
th.text-end.text-card-highlight.text-uppercase.fw-light Start Block
th.text-end.text-card-highlight.fw-light
span.text-uppercase Start Date
small.ms-1 (utc)
//th Date
//th.text-end Block Start
th.text-end.text-card-highlight.text-uppercase.fw-light Difficulty
th.text-end.text-card-highlight.text-uppercase.fw-light Difficulty Δ
small.ms-1 (%)
tbody(id="difficulty-table-body")
tr(id="difficulty-table-row-prototype", style="display: none;")
td.text-end.data-epoch
td.text-end.data-start-block
td.text-end.data-start-date
//td.data-date
//td.text-end.data-block-start
td.text-end.data-difficulty
td.text-end.data-difficulty-delta
tr(id="empty-row-to-fix-striped-coloring", style="display: none;")
block endOfBody
+graphPageScriptSetup
script(src='./js/decimal.js')
script.
Chart.defaults.defaultFontSize = 14;
script.
var blockCount = !{blockCount};
var yearItems = !{JSON.stringify(yearItems)};
var heights = [];
var height = 0;
var chunkSize = 10;
var summary = null;
while (height <= blockCount) {
heights.push([height]);
height += !{coinConfig.difficultyAdjustmentBlockCount};
}
$(document).ready(function() {
var heightChunks = [];
var currentChunk = [];
heightChunks.push(currentChunk);
for (var i = 0; i < heights.length; i++) {
if (currentChunk.length == chunkSize) {
currentChunk = [];
heightChunks.push(currentChunk);
}
currentChunk.push(heights[i]);
}
loadData(heightChunks.map(x => x.join(",")));
});
function loadData(chunkStrs) {
//console.log(JSON.stringify(chunkStrs));
var results = {heights:[]};
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), chunkStrs.length).toLocaleString()} of ${chunkStrs.length.toLocaleString()} (${wPercent})`);
};
var finishedCallback = function() {
summary = summarizeData(results);
fillDifficultyTable(summary);
for (var i = 0; i < yearItems.length; i++) {
createGraph(`graph-diff-hist-${yearItems[i][1]}`, [summary.graphData_years[yearItems[i][1]]], "Difficulty", true);
}
createGraph("graph-diff-change", [summary.changeGraphData], "Difficulty Change %", false);
//createGraph("graph-diff-hist-2", summary.epochChunks, "Difficulty 2");
$("#main-content").show();
$("#progress-wrapper").hide();
// only show the first graph (All Time)
$(`.graph-diff-hist`).hide();
$(`#graph-diff-hist-${yearItems[0][1]}`).show();
$(`#diff-hist-selector-${yearItems[0][1]}`).removeClass("btn-outline-primary").addClass("btn-primary");
};
getData(results, chunkStrs, 0, statusCallback, finishedCallback);
}
function graphDiffHistory(numYears) {
let data = [];
let yearCount = 0;
for (let i = summary.graphData_years.length - 1; i >= 0; i--) {
data = data.concat(summary.graphData_years[i]);
yearCount++;
if (yearCount == numYears) {
break;
}
}
createGraph("graph-diff-hist", [data], "Difficulty", true);
}
function fillDifficultyTable(summary) {
for (var i = 0; i < summary.difficultyData.length; i++) {
var item = summary.difficultyData[i];
var deltaItem = summary.difficultyDeltaData[i];
var row = $("#difficulty-table-row-prototype").clone();
row.attr("id", null);
//row.addClass("fee-rate-table-row");
var startBlock = 2016 * i;
row.find(".data-epoch").text(item.epoch);
row.find(".data-start-block").html(`<a href="./block-height/${startBlock}">${startBlock.toLocaleString()}</a>`);
row.find(".data-start-date").text(new Date(item.date * 1000).toISOString().slice(0, 10));
//row.find(".data-date").text(item.count.toLocaleString());
//row.find(".data-block-start").text(sumBlockCount.toDP(2));
if (item.difficulty >= 1000000) {
row.find(".data-difficulty").text(parseInt(item.difficulty).toLocaleString());
} else {
row.find(".data-difficulty").text(new Decimal(item.difficulty).toDP(3));
}
if (deltaItem.difficultyDelta) {
if (deltaItem.difficultyDelta > 0) {
row.find(".data-difficulty-delta").text("+" + (new Decimal(deltaItem.difficultyDelta).toDP(2)));
row.find(".data-difficulty-delta").addClass("text-success");
} else if (deltaItem.difficultyDelta < 0) {
row.find(".data-difficulty-delta").text(new Decimal(deltaItem.difficultyDelta).toDP(2));
row.find(".data-difficulty-delta").addClass("text-danger");
}
} else {
row.find(".data-difficulty-delta").text("-");
}
row.show();
$("#difficulty-table-body").append(row);
}
}
function fillDifficultyDeltaTable(summary) {
for (var i = 0; i < summary.difficultyDeltaData.length; i++) {
var item = summary.difficultyDeltaData[i];
var row = $("#difficulty-delta-table-row-prototype").clone();
row.attr("id", null);
//row.addClass("fee-rate-table-row");
row.find(".data-epoch").text(item.epoch);
//row.find(".data-date").text(item.count.toLocaleString());
//row.find(".data-block-start").text(sumBlockCount.toDP(2));
if (item.difficultyDelta) {
if (item.difficultyDelta > 0) {
row.find(".data-difficulty-delta").text("+" + (new Decimal(item.difficultyDelta).toDP(2)));
row.find(".data-difficulty-delta").addClass("text-success");
} else if (item.difficultyDelta < 0) {
row.find(".data-difficulty-delta").text(new Decimal(item.difficultyDelta).toDP(2));
row.find(".data-difficulty-delta").addClass("text-danger");
}
} else {
row.find(".data-difficulty-delta").text("-");
}
row.show();
$("#difficulty-delta-table-body").append(row);
}
}
var chartsById = {};
function createGraph(graphId, datas, yLabelStr, logY) {
if (chartsById[graphId]) {
chartsById[graphId].destroy();
}
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: 0
});
}
var ctx = document.getElementById(graphId).getContext('2d');
var graph = new Chart(ctx, {
type: 'line',
data: {
datasets: datasets
},
options: {
interaction: {
intersect: false,
mode: 'index',
},
plugins: {
legend: { display: false },
},
scales: {
x: {
type: 'linear',
position: 'bottom',
scaleLabel: {
display: true,
labelString: 'Difficulty Epoch'
},
grid: {
color: gridLineColor
},
//ticks: {
// stepSize: 100,
//}
},
y: {
type: logY ? "logarithmic" : "linear",
scaleLabel: {
display: true,
labelString: yLabelStr
},
grid: {
color: gridLineColor
},
ticks: {
callback: function(value, index, values) {
if (value > 1000) {
var exp = Math.floor(Math.log10(value));
return (value / Math.pow(10, exp)).toLocaleString() + "e" + exp;
} else {
return value;
}
}
}
}
}
}
});
chartsById[graphId] = graph;
}
function getData(results, chunks, chunkIndex, statusCallback, finishedCallback) {
if (chunkIndex > chunks.length - 1) {
finishedCallback();
return;
}
var url = `./internal-api/difficulty-by-height/${chunks[chunkIndex]}`;
//console.log(url);
$.ajax({
url: url
}).done(function(result) {
for (var height in result) {
results.heights.push(parseInt(height));
results[height] = result[height];
}
statusCallback(chunkIndex, chunks.length);
getData(results, chunks, chunkIndex + 1, statusCallback, finishedCallback);
});
}
function summarizeData(raw) {
raw.heights.sort((a, b) => { a - b });
var summary = {};
summary.difficultyData = [];
summary.difficultyDeltaData = [];
summary.graphData = [];
summary.graphData_years = {};
for (let i = 0; i < yearItems.length; i++) {
summary.graphData_years[yearItems[i][1]] = [];
}
summary.changeGraphData = [];
for (var i = 0; i < raw.heights.length; i++) {
var heightStr = `${raw.heights[i]}`;
var previousHeightStr = `${raw.heights[i - 1]}`;
summary.difficultyData.push({epoch:i, date:raw[heightStr].time, difficulty:raw[heightStr].difficulty});
summary.graphData.push({x:i, y:raw[heightStr].difficulty});
var yearIndex = Math.floor((raw.heights.length - i) / 26);
for (let j = 0; j < yearItems.length; j++) {
if (yearIndex < yearItems[j][1]) {
summary.graphData_years[yearItems[j][1]].push({x:i, y:raw[heightStr].difficulty});
}
}
if (i == 0) {
summary.changeGraphData.push({x:i, y:raw[heightStr].difficulty});
summary.difficultyDeltaData.push({epoch:i});
} else {
var d1 = raw[heightStr].difficulty;
var d0 = raw[previousHeightStr].difficulty
var deltaPercent = 100 * (d1 / d0 - 1);
summary.difficultyDeltaData.push({epoch:i, difficultyDelta:deltaPercent});
/*if (d1 > d0) {
// increase
deltaPercent =
} else {
// decrease
deltaPercent = -100 * (1 - d1 / d0);
}*/
if (deltaPercent > 100) {
deltaPercent = 100;
}
if (deltaPercent < -100) {
deltaPercent = -100;
}
summary.changeGraphData.push({x:i, y:deltaPercent});
}
}
console.log(summary.graphData_years);
/*
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;
}