main_halving_calculator.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2021 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify it under
  7. # the terms of the GNU General Public License as published by the Free Software
  8. # Foundation, either version 3 of the License, or (at your option) any later
  9. # version.
  10. #
  11. # This program is distributed in the hope that it will be useful, but WITHOUT
  12. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  13. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  14. # details.
  15. #
  16. # You should have received a copy of the GNU General Public License along with
  17. # this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. mmnode-halving-calculator: Estimate date(s) of future block subsidy halving(s)
  20. """
  21. import time
  22. from mmgen.cfg import Config
  23. from mmgen.util import async_run
  24. bdr_proj = 9.95
  25. opts_data = {
  26. 'sets': [('mined', True, 'list', True)],
  27. 'text': {
  28. 'desc': 'Estimate date(s) of future block subsidy halving(s)',
  29. 'usage':'[opts]',
  30. 'options': f"""
  31. -h, --help Print this help message
  32. --, --longhelp Print help message for long options (common options)
  33. -l, --list List historical and projected halvings
  34. -m, --mined Same as above, plus list coins mined
  35. -r, --bdr-proj=I Block discovery interval for projected halvings (default:
  36. {bdr_proj:.5f} min)
  37. -s, --sample-size=N Block range to calculate block discovery interval for next
  38. halving estimate (default: dynamically calculated)
  39. """}
  40. }
  41. cfg = Config(opts_data=opts_data)
  42. if cfg.bdr_proj:
  43. bdr_proj = float(cfg.bdr_proj)
  44. def date(t):
  45. return '{}-{:02}-{:02} {:02}:{:02}:{:02}'.format(*time.gmtime(t)[:6])
  46. def dhms(t):
  47. t, neg = (-t, '-') if t < 0 else (t, ' ')
  48. return f'{neg}{t//60//60//24} days, {t//60//60%24:02}:{t//60%60:02}:{t%60:02} h/m/s'
  49. def time_diff_warning(t_diff):
  50. if abs(t_diff) > 60*60:
  51. print('Warning: block tip time is {} {} clock time!'.format(
  52. dhms(abs(t_diff)),
  53. ('behind', 'ahead of')[t_diff<0]))
  54. async def main():
  55. proto = cfg._proto
  56. from mmgen.rpc import rpc_init
  57. c = await rpc_init(cfg, proto, ignore_wallet=True)
  58. tip = await c.call('getblockcount')
  59. assert tip > 1, 'block tip must be > 1'
  60. remaining = proto.halving_interval - tip % proto.halving_interval
  61. sample_size = int(cfg.sample_size) if cfg.sample_size else min(tip-1, max(remaining, 144))
  62. cur, old = await c.gathered_call('getblockstats', ((tip,), (tip - sample_size,)))
  63. clock_time = int(time.time())
  64. time_diff_warning(clock_time - cur['time'])
  65. bdr = (cur['time'] - old['time']) / sample_size
  66. t_rem = remaining * int(bdr)
  67. t_next = cur['time'] + t_rem
  68. if proto.name == 'BitcoinCash':
  69. sub = proto.coin_amt(str(cur['subsidy']))
  70. else:
  71. sub = cur['subsidy'] * proto.coin_amt.satoshi
  72. def print_current_stats():
  73. print(
  74. f'Current block: {tip:>7}\n'
  75. f'Next halving block: {tip + remaining:>7}\n'
  76. f'Halving interval: {proto.halving_interval:>7}\n'
  77. f'Blocks since last halving: {proto.halving_interval - remaining:>7}\n'
  78. f'Blocks until next halving: {remaining:>7}\n\n'
  79. f'Current block subsidy: {str(sub).rstrip("0")} {proto.coin}\n'
  80. f'Current block discovery interval (over last {sample_size} blocks): {bdr/60:0.2f} min\n\n'
  81. f'Current clock time (UTC): {date(clock_time)}\n'
  82. f'Est. halving date (UTC): {date(t_next)}\n'
  83. f'Est. time until halving: {dhms(cur["time"] + t_rem - clock_time)}')
  84. async def print_halvings():
  85. halving_blocknums = [i*proto.halving_interval for i in range(proto.max_halvings+1)][1:]
  86. hist_halvings = await c.gathered_call('getblockstats',([(n,) for n in halving_blocknums if n <= tip]))
  87. halving_secs = bdr_proj * 60 * proto.halving_interval
  88. nhist = len(hist_halvings)
  89. nSubsidy = int(proto.start_subsidy / proto.coin_amt.satoshi)
  90. block0_hash = await c.call('getblockhash', 0)
  91. block0_date = (await c.call('getblock', block0_hash))['time']
  92. def gen_data():
  93. total_mined = 0
  94. date = block0_date
  95. for n, blk in enumerate(halving_blocknums):
  96. mined = (nSubsidy >> n) * proto.halving_interval
  97. if n == 0:
  98. mined -= nSubsidy # subtract unspendable genesis block subsidy
  99. total_mined += mined
  100. sub = nSubsidy >> n+1 if n+1 < proto.max_halvings else 0
  101. bdi = (
  102. (hist_halvings[n]['time'] - date) / (proto.halving_interval * 60) if n < nhist
  103. else bdr/60 if n == nhist
  104. else bdr_proj)
  105. date = (
  106. hist_halvings[n]['time'] if n < nhist
  107. else t_next + int((n - nhist) * halving_secs))
  108. yield (n, sub, blk, mined, total_mined, bdi, date)
  109. if sub == 0:
  110. break
  111. fs = (
  112. ' {a:<7} {b:>8} {c:19}{d:2} {e:10} {f}',
  113. ' {a:<7} {b:>8} {c:19}{d:2} {e:10} {f:17} {g:17} {h}'
  114. )[bool(cfg.mined)]
  115. print(
  116. f'Historical/Estimated/Projected Halvings ({proto.coin}):\n\n'
  117. + f' Sample size for next halving estimate (E): {sample_size} blocks\n'
  118. + f' Block discovery interval for projected halvings (P): {bdr_proj:.5f} minutes\n\n'
  119. + fs.format(
  120. a = 'HALVING',
  121. b = 'BLOCK',
  122. c = 'DATE',
  123. d = '',
  124. e = 'BDI (mins)',
  125. f = 'SUBSIDY ({proto.coin})',
  126. g = f'MINED ({proto.coin})',
  127. h = f'TOTAL MINED ({proto.coin})')
  128. + '\n'
  129. + fs.format(
  130. a = '-' * 7,
  131. b = '-' * 8,
  132. c = '-' * 19,
  133. d = '-' * 2,
  134. e = '-' * 10,
  135. f = '-' * 17,
  136. g = '-' * 17,
  137. h = '-' * 17)
  138. + '\n'
  139. + '\n'.join(fs.format(
  140. a = n + 1,
  141. b = blk,
  142. c = date(t),
  143. d = ' P' if n > nhist else '' if n < nhist else ' E',
  144. e = f'{bdr:8.5f}',
  145. f = proto.coin_amt(sub, from_unit='satoshi').fmt(2, prec=8),
  146. g = proto.coin_amt(mined, from_unit='satoshi').fmt(8, prec=8),
  147. h = proto.coin_amt(total_mined, from_unit='satoshi').fmt(8, prec=8)
  148. ) for n, sub, blk, mined, total_mined, bdr, t in gen_data()))
  149. if cfg.list:
  150. await print_halvings()
  151. else:
  152. print_current_stats()
  153. async_run(cfg, main)