|
@@ -0,0 +1,182 @@
|
|
|
|
+#!/usr/bin/env python3
|
|
|
|
+#
|
|
|
|
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
|
|
|
+# Copyright (C)2013-2020 The MMGen Project <mmgen@tuta.io>
|
|
|
|
+#
|
|
|
|
+# This program is free software: you can redistribute it and/or modify it under
|
|
|
|
+# the terms of the GNU General Public License as published by the Free Software
|
|
|
|
+# Foundation, either version 3 of the License, or (at your option) any later
|
|
|
|
+# version.
|
|
|
|
+#
|
|
|
|
+# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
|
|
+# details.
|
|
|
|
+#
|
|
|
|
+# You should have received a copy of the GNU General Public License along with
|
|
|
|
+# this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
+
|
|
|
|
+"""
|
|
|
|
+mmnode-halving-calculator: Estimate date(s) of future block subsidy halving(s)
|
|
|
|
+"""
|
|
|
|
+
|
|
|
|
+import time
|
|
|
|
+from decimal import Decimal
|
|
|
|
+from mmgen.common import *
|
|
|
|
+
|
|
|
|
+bdr_proj = 9.95
|
|
|
|
+
|
|
|
|
+opts.init({
|
|
|
|
+ 'sets': [('mined',True,'list',True)],
|
|
|
|
+ 'text': {
|
|
|
|
+ 'desc': 'Estimate date(s) of future block subsidy halving(s)',
|
|
|
|
+ 'usage':'[opts]',
|
|
|
|
+ 'options': f"""
|
|
|
|
+-h, --help Print this help message
|
|
|
|
+--, --longhelp Print help message for long options (common options)
|
|
|
|
+-l, --list List historical and projected halvings
|
|
|
|
+-m, --mined Same as above, plus list coins mined
|
|
|
|
+-r, --bdr-proj=I Block discovery interval for projected halvings (default:
|
|
|
|
+ {bdr_proj:.5f} min)
|
|
|
|
+-s, --sample-size=N Block range to calculate block discovery interval for next
|
|
|
|
+ halving estimate (default: dynamically calculated)
|
|
|
|
+""" }
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+if opt.bdr_proj:
|
|
|
|
+ bdr_proj = float(opt.bdr_proj)
|
|
|
|
+
|
|
|
|
+def date(t):
|
|
|
|
+ return '{}-{:02}-{:02} {:02}:{:02}:{:02}'.format(*time.gmtime(t)[:6])
|
|
|
|
+
|
|
|
|
+def dhms(t):
|
|
|
|
+ t,neg = (-t,'-') if t < 0 else (t,' ')
|
|
|
|
+ return f'{neg}{t//60//60//24} days, {t//60//60%24:02}:{t//60%60:02}:{t%60:02} h/m/s'
|
|
|
|
+
|
|
|
|
+def time_diff_warning(t_diff):
|
|
|
|
+ if abs(t_diff) > 60*60:
|
|
|
|
+ print('Warning: block tip time is {} {} clock time!'.format(
|
|
|
|
+ dhms(abs(t_diff)),
|
|
|
|
+ ('behind','ahead of')[t_diff<0]))
|
|
|
|
+
|
|
|
|
+async def main():
|
|
|
|
+
|
|
|
|
+ from mmgen.protocol import init_proto_from_opts
|
|
|
|
+ proto = init_proto_from_opts()
|
|
|
|
+
|
|
|
|
+ from mmgen.rpc import rpc_init
|
|
|
|
+ c = await rpc_init(proto)
|
|
|
|
+
|
|
|
|
+ tip = await c.call('getblockcount')
|
|
|
|
+ assert tip > 1, 'block tip must be > 1'
|
|
|
|
+ remaining = proto.halving_interval - tip % proto.halving_interval
|
|
|
|
+ sample_size = int(opt.sample_size) if opt.sample_size else min(tip-1,max(remaining,144))
|
|
|
|
+
|
|
|
|
+ cur,old = await c.gathered_call('getblockstats',((tip,),(tip - sample_size,)))
|
|
|
|
+
|
|
|
|
+ clock_time = int(time.time())
|
|
|
|
+ time_diff_warning(clock_time - cur['time'])
|
|
|
|
+ bdr = (cur['time'] - old['time']) / sample_size
|
|
|
|
+ t_rem = remaining * int(bdr)
|
|
|
|
+ t_next = cur['time'] + t_rem
|
|
|
|
+
|
|
|
|
+ if proto.name == 'BitcoinCash':
|
|
|
|
+ sub = proto.coin_amt(str(cur['subsidy']))
|
|
|
|
+ else:
|
|
|
|
+ sub = cur['subsidy'] * proto.coin_amt.min_coin_unit
|
|
|
|
+
|
|
|
|
+ def print_current_stats():
|
|
|
|
+ print(
|
|
|
|
+ f'Current block: {tip:>7}\n'
|
|
|
|
+ f'Next halving block: {tip + remaining:>7}\n'
|
|
|
|
+ f'Halving interval: {proto.halving_interval:>7}\n'
|
|
|
|
+ f'Blocks since last halving: {proto.halving_interval - remaining:>7}\n'
|
|
|
|
+ f'Blocks until next halving: {remaining:>7}\n\n'
|
|
|
|
+ f'Current block subsidy: {str(sub).rstrip("0")} {proto.coin}\n'
|
|
|
|
+ f'Current block discovery interval (over last {sample_size} blocks): {bdr/60:0.2f} min\n\n'
|
|
|
|
+ f'Current clock time (UTC): {date(clock_time)}\n'
|
|
|
|
+ f'Est. halving date (UTC): {date(t_next)}\n'
|
|
|
|
+ f'Est. time until halving: {dhms(cur["time"] + t_rem - clock_time)}'
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ async def print_halvings():
|
|
|
|
+ halving_blocknums = [i*proto.halving_interval for i in range(proto.max_halvings+1)][1:]
|
|
|
|
+ hist_halvings = await c.gathered_call('getblockstats',([(n,) for n in halving_blocknums if n <= tip]))
|
|
|
|
+ halving_secs = bdr_proj * 60 * proto.halving_interval
|
|
|
|
+ nhist = len(hist_halvings)
|
|
|
|
+ nSubsidy = int(proto.start_subsidy / proto.coin_amt.min_coin_unit)
|
|
|
|
+
|
|
|
|
+ block0_hash = await c.call('getblockhash',0)
|
|
|
|
+ block0_date = (await c.call('getblock',block0_hash))['time']
|
|
|
|
+
|
|
|
|
+ def gen_data():
|
|
|
|
+ total_mined = 0
|
|
|
|
+ date = block0_date
|
|
|
|
+ for n,blk in enumerate(halving_blocknums):
|
|
|
|
+ mined = (nSubsidy >> n) * proto.halving_interval
|
|
|
|
+ if n == 0:
|
|
|
|
+ mined -= nSubsidy # subtract unspendable genesis block subsidy
|
|
|
|
+ total_mined += mined
|
|
|
|
+ sub = nSubsidy >> n+1 if n+1 < proto.max_halvings else 0
|
|
|
|
+ bdi = (
|
|
|
|
+ (hist_halvings[n]['time'] - date) / (proto.halving_interval * 60) if n < nhist
|
|
|
|
+ else bdr/60 if n == nhist
|
|
|
|
+ else bdr_proj
|
|
|
|
+ )
|
|
|
|
+ date = (
|
|
|
|
+ hist_halvings[n]['time'] if n < nhist
|
|
|
|
+ else t_next + int((n - nhist) * halving_secs)
|
|
|
|
+ )
|
|
|
|
+ yield ( n, sub, blk, mined, total_mined, bdi, date )
|
|
|
|
+ if sub == 0:
|
|
|
|
+ break
|
|
|
|
+
|
|
|
|
+ fs = (
|
|
|
|
+ ' {a:<7} {b:>8} {c:19}{d:2} {e:10} {f}',
|
|
|
|
+ ' {a:<7} {b:>8} {c:19}{d:2} {e:10} {f:17} {g:17} {h}'
|
|
|
|
+ )[bool(opt.mined)]
|
|
|
|
+
|
|
|
|
+ print(
|
|
|
|
+ f'Historical/Estimated/Projected Halvings ({proto.coin}):\n\n'
|
|
|
|
+ + f' Sample size for next halving estimate (E): {sample_size} blocks\n'
|
|
|
|
+ + f' Block discovery interval for projected halvings (P): {bdr_proj:.5f} minutes\n\n'
|
|
|
|
+ + fs.format(
|
|
|
|
+ a = 'HALVING',
|
|
|
|
+ b = 'BLOCK',
|
|
|
|
+ c = 'DATE',
|
|
|
|
+ d = '',
|
|
|
|
+ e = f'BDI (min)',
|
|
|
|
+ f = f'SUBSIDY ({proto.coin})',
|
|
|
|
+ g = f'MINED ({proto.coin})',
|
|
|
|
+ h = f'TOTAL MINED ({proto.coin})'
|
|
|
|
+ )
|
|
|
|
+ + '\n'
|
|
|
|
+ + fs.format(
|
|
|
|
+ a = '-' * 7,
|
|
|
|
+ b = '-' * 8,
|
|
|
|
+ c = '-' * 19,
|
|
|
|
+ d = '-' * 2,
|
|
|
|
+ e = '-' * 10,
|
|
|
|
+ f = '-' * 13,
|
|
|
|
+ g = '-' * 17,
|
|
|
|
+ h = '-' * 17
|
|
|
|
+ )
|
|
|
|
+ + '\n'
|
|
|
|
+ + '\n'.join(fs.format(
|
|
|
|
+ a = n + 1,
|
|
|
|
+ b = blk,
|
|
|
|
+ c = date(t),
|
|
|
|
+ d = ' P' if n > nhist else '' if n < nhist else ' E',
|
|
|
|
+ e = f'{bdr:8.5f}',
|
|
|
|
+ f = proto.coin_amt(sub,from_unit='satoshi').fmt(fs='2.8'),
|
|
|
|
+ g = proto.coin_amt(mined,from_unit='satoshi').fmt(fs='8.8'),
|
|
|
|
+ h = proto.coin_amt(total_mined,from_unit='satoshi').fmt(fs='8.8')
|
|
|
|
+ ) for n,sub,blk,mined,total_mined,bdr,t in gen_data())
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+ if opt.list:
|
|
|
|
+ await print_halvings()
|
|
|
|
+ else:
|
|
|
|
+ print_current_stats()
|
|
|
|
+
|
|
|
|
+run_session(main())
|