ct_regtest.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen-node-tools
  9. # https://gitlab.com/mmgen/mmgen-node-tools
  10. """
  11. test.cmdtest_d.ct_regtest: Regtest tests for the cmdtest.py test suite
  12. """
  13. import sys,os
  14. from decimal import Decimal
  15. from mmgen.util import msg_r,die,gmsg
  16. from mmgen.protocol import init_proto
  17. from mmgen.proto.btc.regtest import MMGenRegtest
  18. from ..include.common import cfg,imsg,stop_test_daemons,joinpath
  19. from .ct_base import CmdTestBase
  20. args1 = ['--bob']
  21. args2 = ['--bob','--rpc-backend=http']
  22. def gen_addrs(proto,network,keys):
  23. from mmgen.tool.api import tool_api
  24. tool = tool_api(cfg)
  25. tool.init_coin(proto.coin,'regtest')
  26. tool.addrtype = proto.mmtypes[-1]
  27. return [tool.privhex2addr('{:064x}'.format(key)) for key in keys]
  28. class CmdTestRegtest(CmdTestBase):
  29. 'various operations via regtest mode'
  30. networks = ('btc','ltc','bch')
  31. passthru_opts = ('coin',)
  32. tmpdir_nums = [1]
  33. color = True
  34. deterministic = False
  35. bdb_wallet = True
  36. cmd_group_in = (
  37. ('setup', 'regtest mode setup'),
  38. ('subgroup.netrate', []),
  39. ('subgroup.halving_calculator', []),
  40. ('subgroup.fund_addrbal', []),
  41. ('subgroup.addrbal', ['fund_addrbal']),
  42. ('subgroup.blocks_info', ['addrbal']),
  43. ('subgroup.feeview', []),
  44. ('stop', 'stopping regtest daemon'),
  45. )
  46. cmd_subgroups = {
  47. 'netrate': (
  48. "'mmnode-netrate' script",
  49. ('netrate1', "netrate (--help)"),
  50. ('netrate2', "netrate"),
  51. ),
  52. 'halving_calculator': (
  53. "'mmnode-halving-calculator' script",
  54. ('halving_calculator1', "halving calculator (--help)"),
  55. ('halving_calculator2', "halving calculator"),
  56. ('halving_calculator3', "halving calculator (--list)"),
  57. ('halving_calculator4', "halving calculator (--mined)"),
  58. ('halving_calculator5', "halving calculator (--mined --bdr-proj=5)"),
  59. ('halving_calculator6', "halving calculator (--mined --sample-size=20)"),
  60. ),
  61. 'fund_addrbal': (
  62. "funding addresses for 'addrbal' subgroup",
  63. ('sendto1', 'sending funds to address #1 (1)'),
  64. ('sendto2', 'sending funds to address #1 (2)'),
  65. ('sendto3', 'sending funds to address #2'),
  66. ),
  67. 'addrbal': (
  68. "'mmnode-addrbal' script",
  69. ('addrbal_single', 'getting address balance (single address)'),
  70. ('addrbal_multiple', 'getting address balances (multiple addresses)'),
  71. ('addrbal_multiple_tabular1', 'getting address balances (multiple addresses, tabular output)'),
  72. ('addrbal_multiple_tabular2', 'getting address balances (multiple addresses, tabular, show first block)'),
  73. ('addrbal_nobal1', 'getting address balances (no balance)'),
  74. ('addrbal_nobal2', 'getting address balances (no balances)'),
  75. ('addrbal_nobal3', 'getting address balances (one null balance)'),
  76. ('addrbal_nobal3_tabular1', 'getting address balances (one null balance, tabular output)'),
  77. ('addrbal_nobal3_tabular2', 'getting address balances (one null balance, tabular, show first block)'),
  78. ),
  79. 'blocks_info': (
  80. "'mmnode-blocks-info' script",
  81. ('blocks_info1', "blocks-info (--help)"),
  82. ('blocks_info2', "blocks-info (no args)"),
  83. ('blocks_info3', "blocks-info +100"),
  84. ('blocks_info4', "blocks-info --miner-info --fields=all --stats=all +1"),
  85. ),
  86. 'feeview': (
  87. "'mmnode-feeview' script",
  88. ('feeview_setup', 'setting up feeview test'),
  89. ('feeview1', "'mmnode-feeview'"),
  90. ('feeview2', "'mmnode-feeview --columns=40 --include-current'"),
  91. ('feeview3', "'mmnode-feeview --precision=6'"),
  92. ('feeview4', "'mmnode-feeview --detail'"),
  93. ('feeview5', "'mmnode-feeview --show-empty --log'"),
  94. ('feeview6', "'mmnode-feeview --ignore-below=1MB'"),
  95. ('feeview7', "'mmnode-feeview --ignore-below=20kB'"),
  96. ('feeview8', "'mmnode-feeview' (empty mempool)"),
  97. ),
  98. }
  99. def __init__(self,trunner,cfgs,spawn):
  100. CmdTestBase.__init__(self,trunner,cfgs,spawn)
  101. if trunner == None:
  102. return
  103. if cfg._proto.testnet:
  104. die(2,'--testnet and --regtest options incompatible with regtest test suite')
  105. self.proto = init_proto( cfg, self.proto.coin, network='regtest', need_amt=True )
  106. self.addrs = [a.views[a.view_pref] for a in gen_addrs(self.proto,'regtest',[1,2,3,4,5])]
  107. self.use_bdb_wallet = self.bdb_wallet or self.proto.coin != 'BTC'
  108. self.regtest = MMGenRegtest(cfg, self.proto.coin, bdb_wallet=self.use_bdb_wallet)
  109. def setup(self):
  110. stop_test_daemons(self.proto.network_id,force=True,remove_datadir=True)
  111. from shutil import rmtree
  112. try:
  113. rmtree(joinpath(self.tr.data_dir,'regtest'))
  114. except:
  115. pass
  116. t = self.spawn(
  117. 'mmgen-regtest',
  118. (['--bdb-wallet'] if self.use_bdb_wallet else [])
  119. + ['--setup-no-stop-daemon', 'setup'])
  120. for s in ('Starting','Creating','Creating','Creating','Mined','Setup complete'):
  121. t.expect(s)
  122. return t
  123. def netrate(self, add_args, expect_str, exit_val=None):
  124. t = self.spawn('mmnode-netrate', args1 + add_args, exit_val=exit_val)
  125. t.expect(expect_str,regex=True)
  126. return t
  127. def netrate1(self):
  128. return self.netrate( ['--help'], 'USAGE:.*' )
  129. def netrate2(self):
  130. t = self.netrate([], r'sent:.*', exit_val=-15)
  131. t.kill(15)
  132. if sys.platform == 'win32':
  133. return 'ok'
  134. return t
  135. def halving_calculator(self,add_args,expect_list):
  136. t = self.spawn('mmnode-halving-calculator',args1+add_args)
  137. t.match_expect_list(expect_list)
  138. return t
  139. def halving_calculator1(self):
  140. return self.halving_calculator(['--help'],['USAGE:'])
  141. def halving_calculator2(self):
  142. return self.halving_calculator([],['Current block: 393',f'Current block subsidy: 12.5 {cfg.coin}'])
  143. def halving_calculator3(self):
  144. return self.halving_calculator(['--list'],['33 4950','0'])
  145. def halving_calculator4(self):
  146. return self.halving_calculator(['--mined'],['0 0.0000015 14949.9999835'])
  147. def halving_calculator5(self):
  148. return self.halving_calculator(['--mined','--bdr-proj=5'],['5.00000 0 0.0000015 14949.9999835'])
  149. def halving_calculator6(self):
  150. return self.halving_calculator(['--mined','--sample-size=20'],['33 4950','0 0.0000015 14949.9999835'])
  151. def sendto(self,addr,amt):
  152. return self.spawn('mmgen-regtest',['send',addr,amt])
  153. def sendto1(self): return self.sendto(self.addrs[0],'0.123')
  154. def sendto2(self): return self.sendto(self.addrs[0],'0.234')
  155. def sendto3(self): return self.sendto(self.addrs[1],'0.345')
  156. def addrbal(self, args, expect_list):
  157. t = self.spawn('mmnode-addrbal', args2 + args)
  158. t.match_expect_list(expect_list)
  159. return t
  160. def addrbal_single(self):
  161. return self.addrbal(
  162. [self.addrs[0]],
  163. [
  164. f'Balance: 0.357 {cfg.coin}',
  165. '2 unspent outputs in 2 blocks',
  166. '394', '0.123',
  167. '395', '0.234'
  168. ])
  169. def addrbal_multiple(self):
  170. return self.addrbal(
  171. [self.addrs[1], self.addrs[0]],
  172. [
  173. '396', '0.345',
  174. '394', '0.123',
  175. '395', '0.234'
  176. ])
  177. def addrbal_multiple_tabular1(self):
  178. return self.addrbal(
  179. ['--tabular', self.addrs[1], self.addrs[0]],
  180. [
  181. self.addrs[1] + ' 1 396', '0.345',
  182. self.addrs[0] + ' 2 395', '0.357'
  183. ])
  184. def addrbal_multiple_tabular2(self):
  185. return self.addrbal(
  186. ['--tabular', '--first-block', self.addrs[1], self.addrs[0]],
  187. [
  188. self.addrs[1] + ' 1 396', '396', '0.345',
  189. self.addrs[0] + ' 2 394', '395', '0.357'
  190. ])
  191. def addrbal_nobal1(self):
  192. return self.addrbal(
  193. [self.addrs[2]], ['Address has no balance'])
  194. def addrbal_nobal2(self):
  195. return self.addrbal(
  196. [self.addrs[2], self.addrs[3]], ['Addresses have no balances'])
  197. def addrbal_nobal3(self):
  198. return self.addrbal(
  199. [self.addrs[4], self.addrs[0], self.addrs[3]],
  200. [
  201. 'No balance',
  202. '2 unspent outputs in 2 blocks',
  203. '394','0.123','395','0.234',
  204. 'No balance'
  205. ])
  206. def addrbal_nobal3_tabular1(self):
  207. return self.addrbal(
  208. ['--tabular', self.addrs[4], self.addrs[0], self.addrs[3]],
  209. [
  210. self.addrs[4] + ' - - -',
  211. self.addrs[0] + ' 2 395','0.357',
  212. self.addrs[3] + ' - - -',
  213. ])
  214. def addrbal_nobal3_tabular2(self):
  215. return self.addrbal(
  216. ['--tabular', '--first-block', self.addrs[4], self.addrs[0], self.addrs[3]],
  217. [
  218. self.addrs[4] + ' - - - -',
  219. self.addrs[0] + ' 2 394','395','0.357',
  220. self.addrs[3] + ' - - - -',
  221. ])
  222. def blocks_info(self,args,expect_list):
  223. t = self.spawn('mmnode-blocks-info', args1 + args)
  224. t.match_expect_list(expect_list)
  225. return t
  226. def blocks_info1(self):
  227. return self.blocks_info(
  228. ['--help'],
  229. ['USAGE:','OPTIONS:'])
  230. def blocks_info2(self):
  231. return self.blocks_info(
  232. [],
  233. ['Current height: 396'])
  234. def blocks_info3(self):
  235. return self.blocks_info(
  236. ['+100'],
  237. [
  238. 'Range: 297-396',
  239. 'Current height: 396',
  240. 'Next diff adjust: 2016'
  241. ])
  242. def blocks_info4(self):
  243. n1,i1,o1,n2,i2,o2 = (2,1,3,6,3,9) if cfg.coin == 'BCH' else (2,1,4,6,3,12)
  244. return self.blocks_info(
  245. ['--miner-info', '--fields=all', '--stats=all', '+3'],
  246. [
  247. 'Averages',
  248. f'nTx: {n1}',
  249. f'Inputs: {i1}',
  250. f'Outputs: {o1}',
  251. 'Totals',
  252. f'nTx: {n2}',
  253. f'Inputs: {i2}',
  254. f'Outputs: {o2}',
  255. 'Current height: 396',
  256. 'Next diff adjust: 2016'
  257. ])
  258. async def feeview_setup(self):
  259. def create_pairs(nPairs):
  260. from mmgen.tool.api import tool_api
  261. from collections import namedtuple
  262. t = tool_api(cfg)
  263. t.init_coin(self.proto.coin,self.proto.network)
  264. t.addrtype = 'compressed' if self.proto.coin == 'BCH' else 'bech32'
  265. wp = namedtuple('wifaddrpair',['wif','addr'])
  266. def gen():
  267. for n in range(0xfaceface,nPairs+0xfaceface):
  268. wif = t.hex2wif(f'{n:064x}')
  269. yield wp( wif, t.wif2addr(wif) )
  270. return list(gen())
  271. def gen_fees(n_in,low,high):
  272. # very approximate tx size estimation:
  273. ibytes,wbytes,obytes = (148,0,34) if self.proto.coin == 'BCH' else (43,108,31)
  274. x = (ibytes + (wbytes//4) + (obytes * nPairs)) * self.proto.coin_amt.satoshi
  275. n = n_in - 1
  276. vmax = high - low
  277. for i in range(n_in):
  278. yield Decimal(low + (i/n)**6 * vmax) * x
  279. async def do_tx(inputs,outputs,wif):
  280. tx_hex = await r.rpc_call( 'createrawtransaction', inputs, outputs )
  281. tx = await r.rpc_call( 'signrawtransactionwithkey', tx_hex, [wif], [], self.proto.sighash_type )
  282. assert tx['complete'] == True
  283. return tx['hex']
  284. async def do_tx1():
  285. us = await r.rpc_call('listunspent',wallet='miner')
  286. tx_input = us[7] # 25 BTC in coinbase -- us[0] could have < 25 BTC
  287. fee = self.proto.coin_amt('0.001')
  288. outputs = {p.addr:tx1_amt for p in pairs[:nTxs]}
  289. outputs.update({burn_addr: self.proto.coin_amt(tx_input['amount']) - (tx1_amt*nTxs) - fee})
  290. return await do_tx(
  291. [{ 'txid': tx_input['txid'], 'vout': 0 }],
  292. outputs,
  293. await r.miner_wif)
  294. async def do_tx2(tx,pairno):
  295. fee = self.proto.coin_amt(fees[pairno], from_decimal=True)
  296. outputs = {p.addr:tx2_amt for p in pairs}
  297. outputs.update({burn_addr: tx1_amt - (tx2_amt*len(pairs)) - fee})
  298. return await do_tx(
  299. [{ 'txid': tx['txid'], 'vout': pairno }],
  300. outputs,
  301. pairs[pairno].wif )
  302. async def do_txs(tx_in):
  303. for pairno in range(nTxs):
  304. tx_hex = await do_tx2(tx_in,pairno)
  305. await r.rpc_call('sendrawtransaction',tx_hex)
  306. self.spawn('',msg_only=True)
  307. r = self.regtest
  308. nPairs = 100
  309. nTxs = 25
  310. tx1_amt = self.proto.coin_amt('{:0.4f}'.format(24 / nTxs)) # 25 BTC subsidy, leave extra for fee
  311. tx2_amt = self.proto.coin_amt('0.00005') # make this as small as possible
  312. imsg(f'Creating {nPairs} key-address pairs')
  313. pairs = create_pairs(nPairs+1)
  314. burn_addr = pairs.pop()[1]
  315. imsg(f'Creating funding transaction with {nTxs} outputs of value {tx1_amt} {self.proto.coin}')
  316. tx1_hex = await do_tx1()
  317. imsg(f'Relaying funding transaction')
  318. await r.rpc_call('sendrawtransaction',tx1_hex)
  319. imsg(f'Mining a block')
  320. await r.generate(1,silent=True)
  321. imsg(f'Generating fees for mempool transactions')
  322. fees = list(gen_fees(nTxs,2,120))
  323. imsg(f'Creating and relaying {nTxs} mempool transactions with {nPairs} outputs each')
  324. await do_txs(await r.rpc_call('decoderawtransaction',tx1_hex))
  325. return 'ok'
  326. def _feeview(self,args,expect_list=[]):
  327. t = self.spawn('mmnode-feeview', args1 + args)
  328. if expect_list:
  329. t.match_expect_list(expect_list)
  330. return t
  331. def feeview1(self):
  332. return self._feeview([])
  333. def feeview2(self):
  334. return self._feeview(['--columns=40','--include-current'])
  335. def feeview3(self):
  336. return self._feeview(['--precision=6'])
  337. def feeview4(self):
  338. return self._feeview(['--detail'])
  339. def feeview5(self):
  340. return self._feeview(['--show-empty','--log',f'--outdir={self.tmpdir}'])
  341. def feeview6(self):
  342. return self._feeview(['--ignore-below=1MB'])
  343. def feeview7(self):
  344. return self._feeview(['--ignore-below=4kB'])
  345. async def feeview8(self):
  346. imsg('Clearing mempool')
  347. await self.regtest.generate(1,silent=True)
  348. return self._feeview([])
  349. def stop(self):
  350. if cfg.no_daemon_stop:
  351. self.spawn('',msg_only=True)
  352. msg_r('(leaving daemon running by user request)')
  353. return 'ok'
  354. else:
  355. return self.spawn('mmgen-regtest',['stop'])