Skip to content

Instantly share code, notes, and snippets.

@Davidson-Souza
Last active July 18, 2021 23:26
Show Gist options
  • Save Davidson-Souza/1319e815179f216224f96613e1f53f9e to your computer and use it in GitHub Desktop.
Save Davidson-Souza/1319e815179f216224f96613e1f53f9e to your computer and use it in GitHub Desktop.
This (terrible) code will show some statistics of the bitcoin mining, in a more or less accurate way. The code is intended to be easy to read and understand.
# In order to run this example in Unix-like systems, like linux, you should click in "raw" and copy-paste the code in a .js file
# like main.js. The run the follow:
npm init
npm install node-bitcoin-rpc bignumber.js
node main.js
/**
This (terrible) code will show some statistics of the bitcoin mining, in a more or less accurate way.
@Author: Davidson Souza (Erik)
*/
/**
NOTE: The actual magic happens within main, these functions are just helping ones
*/
/* The only required nodejs dependency is node-bitcoin-rpc for accessing the data and bignumber.js (and a running node) */
const rpc = require("node-bitcoin-rpc")
/**Edit: This is just a dummy sample, use your own credentials, and never use adifficultpassword as actual password*/
rpc.init("192.168.42.9", 8332, "erik", "adifficultpassword");
/* If your RPC is taking long to respond, you can change the timeout here or by exporting a TIMEOUT env variable */
rpc.setTimeout(Number (process.env.TIMEOUT) || 10000);
const BigNumber = require('bignumber.js');
/** This (even terrible) function will get the best block of the chain */
function getBestBlock()
{
return new Promise ((accept, reject) =>{
rpc.call("getblockchaininfo", [], (e, d) =>
{
if(!e)
accept(d.result.headers);
})
})
}
/** Take the time and the bits form the first block*/
function bestBlockTime(cb)
{
rpc.call("getblockchaininfo", [], (e, d) =>
{
rpc.call("getblockheader", [d.result.bestblockhash], (e, r) =>
{
if(!r) throw e;
cb(parseInt(r.result.time), r.result.bits);
});
});
}
/** Take the time and height form the first block withn the current retargeting epoch */
function fistEpochBlockTime(cb)
{
getBestBlock()
.then((v) =>
{
const first = v - (v % 2016);
rpc.call("getblockhash", [first], (e, d) =>
{
rpc.call("getblockheader", [d.result], (e, d) =>
{
cb(parseInt (d.result.time), (v % 2016));
});
});
})
}
/** Here lies the magic */
async function main()
{
fistEpochBlockTime((first, blocksSoFar) =>
{
bestBlockTime(async (last, bits) =>
{
/** Extracts the exponent and the base from bits */
const exp = parseInt(bits.substr(0, 2), 16);
const base = parseInt(bits.substr(2, 6), 16)
/** The target is encoded using the compact format, unpack then */
let target = BigNumber();
target = base * Math.pow (2, (8*(exp - 3)));
/** This is the greater possible target (i.e the lesser difficulty) */
const diff_1 = BigNumber("0x00000000FFFF0000000000000000000000000000000000000000000000000000")
/** Difficulty is just the lesser possible target divided by the current target
* notice that difficulty is the opposite of the possibility of finding one block.
* In average, you need make this munch hashes to find a valid block. Notice also
* that this diff is calculated as a multiple of the min difficulty. The actual
* one is min_diff * diff
*/
let diff = diff_1/target;
/**
* This is a temporary time window for a given epoch, if we assume each block
* as taking exactly 10 minutes, they should last this munch
*/
const timespan = 10 * 60 * blocksSoFar;
/**
* Here lies the re-targeting thing, because hashrate is not a constant we need
* change the difficulty to compensate fluctuations.
*/
let newTarget = target;
/**
* Again, if the blocks takes 10 minutes to mine to, the difference between
* the time of the fist and the last epoch's block should be *timespan*,
* however, the may take more or less than that. Here we take the actual
* time.
*/
let actual = (last - first);
/**
* Re-targeting is just a matter of multiplying the former target by the
* actual time that took for mining to those blocks, and dividing by the
* pretended one. One can easily derive this whole logic from simple probability
* theory.
*/
newTarget *= actual;
newTarget /= timespan;
/**
* Here we can compute the new difficulty based upon the new target.
* Remember that this is a estimation unless we are actually in the last
* epoch's block.
*/
const newDiff = diff_1/newTarget;
/**
* We can also calculate the percentage of change in difficulty
*/
const percentege = ((newDiff - diff) * 100)/diff;
/**
* That takes *diff* hashes to mine to a new block, and each block takes
* 10 minus in average, or 10 minutes * 60 seconds = 600 seconds. In 10 minutes
* we make *diff* hashes, in a second we make diff/600.
* This 2^32 is for correcting the fact of the diff_1 != 2^32, but about 2^31 or so.
* A more accurate version may have a "correction factor", but let's just prune it here
* More info: https://en.bitcoin.it/wiki/Difficulty
*/
const hashrate = (diff * Math.pow(2, 32))/600
/**
* Let's say you got a Antminer S19 Pro that makes 110TH/s.
* If we divide both sides of the equation above by the difficulty factor
* (the right-side numerator), and take the inverse of the equation (one over it)
* we get: h = D/t => h/D = 1/t => D/h = t, where D = difficult, h is the hashrate
* and t is the time.
* I will show it in years to be more easy to visualize, but this formula gives
* the result in seconds.
*/
const timeToS19Mine = (diff * Math.pow(2, 32))/(110000000000000);
/**
* Print this whole mess
*/
console.log
(
`Each block took: ${(actual/blocksSoFar/60).toPrecision(4)} minutes, on average`,
`\nThe current diff is: ${diff.toPrecision(2)}`,
`\nThe current hashrate is: ${hashrate.toPrecision(2)}`,
`\nThe next diff estimation: ${newDiff.toPrecision(2)}`,
`\nNext re-targeting percentage: ${percentege.toPrecision(4)}%`,
`\nYour poor S19 Pro wold take ${(timeToS19Mine/60/60/24/365).toPrecision(2)} years to mine a single block`,
);
});
});
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment