mirror of
https://github.com/janoside/btc-rpc-explorer.git
synced 2026-08-13 12:33:13 +02:00
-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
458 lines
13 KiB
Text
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;
|
|
}
|
|
|