main_feeview.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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-feeview: Visualize the fee structure of a node’s mempool
  20. """
  21. from mmgen.common import *
  22. from mmgen.util2 import int2bytespec,parse_bytespec
  23. min_prec,max_prec,dfl_prec = (0,6,4)
  24. fee_brackets = [
  25. 1, 2, 3, 4, 5, 6,
  26. 8, 10, 12, 14, 16, 18,
  27. 20, 25, 30, 35, 40, 45,
  28. 50, 60, 70, 80, 90,
  29. 100, 120, 140, 160, 180,
  30. 200, 250, 300, 350, 400, 450,
  31. 500, 600, 700, 800, 900,
  32. 1000, 1200, 1400, 1600, 1800,
  33. 2000, 2500, 3000, 3500, 4000, 4500,
  34. 5000, 6000, 7000, 8000, 9000,
  35. 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000,
  36. 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 2100000000000000,
  37. ]
  38. opts.init({
  39. 'sets': [
  40. ('detail',True,'ranges',True),
  41. ('detail',True,'show_mb_col',True),
  42. ('detail',True,'precision',6),
  43. ],
  44. 'text': {
  45. 'desc': 'Visualize the fee structure of a node’s mempool',
  46. 'usage':'[opts]',
  47. 'options': f"""
  48. -h, --help Print this help message
  49. --, --longhelp Print help message for long options (common options)
  50. -c, --include-current Include current bracket’s TXs in cumulative MB value
  51. -d, --outdir=D Write log data to directory 'D'
  52. -D, --detail Same as --ranges --show-mb-col --precision=6
  53. -e, --show-empty Show all fee brackets, including empty ones
  54. -i, --ignore-below=B Ignore fee brackets with less than 'B' bytes of TXs
  55. -l, --log Log JSON-RPC mempool data to 'mempool.json'
  56. -p, --precision=P Use 'P' decimal points of precision for megabyte amts
  57. (min: {min_prec}, max: {max_prec}, default: {dfl_prec})
  58. -P, --pager Pipe the output to a pager
  59. -r, --ranges Display fee brackets as ranges
  60. -s, --show-mb-col Display column with each fee bracket’s megabyte count
  61. """,
  62. 'notes': """
  63. + By default, fee bracket row labels include only the top of the range.
  64. + By default, empty fee brackets are not displayed.
  65. + Mempool amounts are shown in decimal megabytes.
  66. + Values in the Total MB column are cumulative and represent megabytes of
  67. transactions in the mempool with fees higher than the TOP of the current
  68. fee bracket. To change this behavior, use the --include-current option.
  69. Note that there is no global mempool in Bitcoin, and your node’s mempool may
  70. differ significantly from those of mining nodes depending on uptime and other
  71. factors.
  72. """
  73. }
  74. })
  75. if opt.ignore_below:
  76. if opt.show_empty:
  77. die(1,'Conflicting options: --ignore-below, --show-empty')
  78. ignore_below = parse_bytespec(opt.ignore_below)
  79. precision = (
  80. check_int_between(opt.precision,min_prec,max_prec,'--precision arg')
  81. if opt.precision else dfl_prec )
  82. from mmgen.term import get_terminal_size
  83. width = g.columns or get_terminal_size().width
  84. class fee_bracket:
  85. def __init__(self,top,bottom):
  86. self.top = top
  87. self.bottom = bottom
  88. self.tx_bytes = 0
  89. self.tx_bytes_cum = 0
  90. self.skip = False
  91. def log(data,fn):
  92. import json
  93. from mmgen.rpc import json_encoder
  94. from mmgen.fileutil import write_data_to_file
  95. write_data_to_file(
  96. outfile = fn,
  97. data = json.dumps(data,cls=json_encoder,sort_keys=True,indent=4),
  98. desc = 'mempool',
  99. ask_overwrite = False )
  100. def create_data(coin_amt,mempool):
  101. out = [fee_bracket(fee_brackets[i],fee_brackets[i-1] if i else 0) for i in range(len(fee_brackets))]
  102. # populate fee brackets:
  103. size_key = 'size' if proto.coin == 'BCH' else 'vsize'
  104. for tx in mempool.values():
  105. fee = coin_amt(tx['fees']['base']).to_unit('satoshi')
  106. size = tx[size_key]
  107. for bracket in out:
  108. if fee / size < bracket.top:
  109. bracket.tx_bytes += size
  110. break
  111. # remove empty top brackets:
  112. while out and out[-1].tx_bytes == 0:
  113. out.pop()
  114. out.reverse() # cumulative totals and display are top-down
  115. # calculate cumulative byte totals, filter rows:
  116. tBytes = 0
  117. for i in out:
  118. if not (i.tx_bytes or opt.show_empty):
  119. i.skip = True
  120. if opt.ignore_below and i.tx_bytes < ignore_below:
  121. i.skip = True
  122. i.tx_bytes_cum = tBytes
  123. tBytes += i.tx_bytes
  124. return out
  125. def gen_header(host,mempool,blockcount):
  126. yield(fmt(f"""
  127. Mempool Fee Structure
  128. Date: {make_timestr()} UTC
  129. Host: {host}
  130. Network: {proto.coin.upper()} {proto.network.upper()}
  131. Block: {blockcount}
  132. TX count: {len(mempool)}
  133. """)).strip()
  134. if opt.show_empty:
  135. yield('Displaying all fee brackets')
  136. elif opt.ignore_below:
  137. yield('Ignoring fee brackets with less than {:,} bytes ({})'.format(
  138. ignore_below,
  139. int2bytespec(ignore_below,'MB','0.6',strip=True,add_space=True),
  140. ))
  141. if opt.include_current:
  142. yield('Including transactions in current fee bracket in Total MB amounts')
  143. def fmt_mb(n):
  144. return int2bytespec(n,'MB',f'0.{precision}',print_sym=False)
  145. def gen_body(data):
  146. tx_bytes_max = max((i.tx_bytes for i in data),default=0)
  147. top_max = max((i.top for i in data),default=0)
  148. bot_max = max((i.bottom for i in data),default=0)
  149. col1_w = max(len(f'{bot_max}-{top_max}') if opt.ranges else len(f'{top_max}'),6)
  150. col2_w = len(fmt_mb(tx_bytes_max)) if opt.show_mb_col else 0
  151. col3_w = len(fmt_mb(data[-1].tx_bytes_cum)) if data else 0
  152. col4_w = width - col1_w - col2_w - col3_w - (4 if col2_w else 3)
  153. if opt.show_mb_col:
  154. fs = '{a:<%i} {b:>%i} {c:>%i} {d}' % (col1_w,col2_w,col3_w)
  155. else:
  156. fs = '{a:<%i} {c:>%i} {d}' % (col1_w,col3_w)
  157. yield fs.format(a='', b='', c=f'{"Total":<{col3_w}}', d='')
  158. yield fs.format(a='sat/B', b=f'{"MB":<{col2_w}}', c=f'{"MB":<{col3_w}}', d='')
  159. for i in data:
  160. if not i.skip:
  161. cum_bytes = i.tx_bytes_cum + i.tx_bytes if opt.include_current else i.tx_bytes_cum
  162. yield(fs.format(
  163. a = '{}-{}'.format(i.bottom,i.top) if opt.ranges else i.top,
  164. b = fmt_mb(i.tx_bytes),
  165. c = fmt_mb(cum_bytes),
  166. d = '-' * int(col4_w * ( i.tx_bytes / tx_bytes_max )) ))
  167. yield(fs.format(
  168. a = 'TOTAL',
  169. b = '',
  170. c = fmt_mb(data[-1].tx_bytes_cum + data[-1].tx_bytes if data else 0),
  171. d = '' ))
  172. async def main():
  173. from mmgen.protocol import init_proto_from_opts
  174. global proto
  175. proto = init_proto_from_opts(need_amt=True)
  176. from mmgen.rpc import rpc_init
  177. c = await rpc_init(proto)
  178. mempool = await c.call('getrawmempool',True)
  179. if opt.log:
  180. log(mempool,'mempool.json')
  181. data = create_data(proto.coin_amt,mempool)
  182. stdout_or_pager(
  183. '\n'.join(gen_header(
  184. c.host,
  185. mempool,
  186. await c.call('getblockcount') )) + '\n\n' +
  187. '\n'.join(gen_body(data)) + '\n' )
  188. async_run(main())