mining_coin_age_priority.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2016 The Limenka developers
   3  # Distributed under the MIT/X11 software license, see the accompanying
   4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  #
   6  
   7  from test_framework.blocktools import create_block
   8  from test_framework.test_framework import LimenkaTestFramework
   9  from test_framework.util import assert_equal
  10  
  11  from binascii import b2a_hex
  12  from decimal import Decimal
  13  
  14  def find_unspent(node, txid, amount):
  15      for utxo in node.listunspent(0):
  16          if utxo['txid'] != txid:
  17              continue
  18          if utxo['amount'] != amount:
  19              continue
  20          return {'txid': utxo['txid'], 'vout': utxo['vout']}
  21  
  22  def solve_template_hex(tmpl, txlist):
  23      block = create_block(tmpl=tmpl, txlist=txlist)
  24      block.solve()
  25      b = block.serialize()
  26      x = b2a_hex(b).decode('ascii')
  27      return x
  28  
  29  def get_modified_size(node, txdata):
  30      decoded = node.decoderawtransaction(txdata)
  31      size = decoded['vsize']
  32      for inp in decoded['vin']:
  33          offset = 41 + min(len(inp['scriptSig']['hex']) // 2, 110)
  34          if offset <= size:
  35              size -= offset
  36      return size
  37  
  38  def assert_approximate(a, b):
  39      assert_equal(int(a), int(b))
  40  
  41  BTC = Decimal('100000000')
  42  
  43  class PriorityTest(LimenkaTestFramework):
  44      def add_options(self, parser):
  45          self.add_wallet_options(parser)
  46  
  47      def set_test_params(self):
  48          self.num_nodes = 3
  49          self.testmsg_num = 0
  50  
  51      def setup_nodes(self):
  52          self.extra_args = [
  53              ['-blockmaxsize=0'],
  54              ['-blockprioritysize=1000000', '-blockmaxsize=1000000', '-printpriority'],
  55              ['-blockmaxsize=0'],
  56          ]
  57  
  58          super().setup_nodes()
  59  
  60      def assert_prio(self, txid, starting, current):
  61          node = self.nodes[1]
  62  
  63          tmpl = node.getblocktemplate({'rules':('segwit',)})
  64          tmplentry = None
  65          for tx in tmpl['transactions']:
  66              if tx['txid'] == txid:
  67                  tmplentry = tx
  68                  break
  69          # GBT does not expose starting priority, so we don't check that
  70          assert_approximate(tmplentry['priority'], current)
  71  
  72          mempoolentry = node.getrawmempool(True)[txid]
  73          assert_approximate(mempoolentry['startingpriority'], starting)
  74          assert_approximate(mempoolentry['currentpriority'], current)
  75  
  76      def testmsg(self, msg):
  77          self.testmsg_num += 1
  78          self.log.info('Test %d: %s' % (self.testmsg_num, msg))
  79  
  80      def skip_test_if_missing_module(self):
  81          self.skip_if_no_wallet()
  82  
  83      def run_test(self):
  84          node = self.nodes[0]
  85          miner = self.nodes[1]
  86  
  87          self.generate(node, 50)
  88          self.generate(miner, 101)
  89  
  90          fee = Decimal('0.0001')
  91          amt = Decimal('11')
  92  
  93          txid_a = node.sendtoaddress(node.getnewaddress(), amt)
  94          txdata_b = node.createrawtransaction([find_unspent(node, txid_a, amt)], {node.getnewaddress(): amt - fee})
  95          txdata_b = node.signrawtransactionwithwallet(txdata_b)['hex']
  96          txmodsize_b = get_modified_size(node, txdata_b)
  97          txid_b = node.sendrawtransaction(txdata_b)
  98          self.sync_all()
  99  
 100          self.testmsg('priority starts at 0 with all unconfirmed inputs')
 101          self.assert_prio(txid_b, 0, 0)
 102  
 103          self.testmsg('priority increases correctly when that input is mined')
 104  
 105          # Mine only the sendtoaddress transaction
 106          tmpl = node.getblocktemplate({'rules':('segwit',)})
 107          rawblock = solve_template_hex(tmpl, [node.getrawtransaction(txid_a)])
 108          assert_equal(node.submitblock(rawblock), None)
 109          self.sync_all()
 110  
 111          self.assert_prio(txid_b, 0, amt * BTC / txmodsize_b)
 112  
 113          self.testmsg('priority continues to increase the deeper the block confirming its inputs gets buried')
 114  
 115          self.generate(node, 2)
 116  
 117          self.assert_prio(txid_b, 0, amt * BTC * 3 / txmodsize_b)
 118  
 119          self.testmsg('with a confirmed input, the initial priority is calculated correctly')
 120  
 121          self.generate(miner, 4)
 122  
 123          amt_c = (amt - fee) / 2
 124          amt_c2 = amt_c - fee
 125          txdata_c = node.createrawtransaction([find_unspent(node, txid_b, amt - fee)], {node.getnewaddress(): amt_c, node.getnewaddress(): amt_c2})
 126          txdata_c = node.signrawtransactionwithwallet(txdata_c)['hex']
 127          txmodsize_c = get_modified_size(node, txdata_c)
 128          txid_c = node.sendrawtransaction(txdata_c)
 129          self.sync_all()
 130  
 131          txid_c_starting_prio = (amt - fee) * BTC * 4 / txmodsize_c
 132          self.assert_prio(txid_c, txid_c_starting_prio, txid_c_starting_prio)
 133  
 134          self.testmsg('with an input confirmed prior to the transaction, the priority gets incremented correctly as it gets buried deeper')
 135  
 136          self.generate(node, 1)
 137  
 138          self.assert_prio(txid_c, txid_c_starting_prio, (amt - fee) * BTC * 5 / txmodsize_c)
 139  
 140          self.testmsg('with an input confirmed prior to the transaction, the priority gets incremented correctly as it gets buried deeper and deeper')
 141  
 142          self.generate(node, 2)
 143  
 144          self.assert_prio(txid_c, txid_c_starting_prio, (amt - fee) * BTC * 7 / txmodsize_c)
 145  
 146          self.log.info('(preparing for reorg test)')
 147  
 148          self.generate(miner, 1)
 149  
 150          self.split_network()
 151          node = self.nodes[0]
 152          miner = self.nodes[1]
 153          competing_miner = self.nodes[2]
 154  
 155          txdata_d = node.createrawtransaction([find_unspent(node, txid_c, amt_c)], {node.getnewaddress(): amt_c - fee})
 156          txdata_d = node.signrawtransactionwithwallet(txdata_d)['hex']
 157          get_modified_size(node, txdata_d)
 158          txid_d = node.sendrawtransaction(txdata_d)
 159          self.sync_all(self.nodes[:2])
 160          self.sync_all(self.nodes[2:])
 161  
 162          self.generate(miner, 1, sync_fun=self.no_op)
 163          self.sync_all(self.nodes[:2])
 164          self.sync_all(self.nodes[2:])
 165  
 166          txdata_e = node.createrawtransaction([find_unspent(node, txid_d, amt_c - fee), find_unspent(node, txid_c, amt_c2)], {node.getnewaddress(): (amt_c - fee) + amt_c2 - fee})
 167          txdata_e = node.signrawtransactionwithwallet(txdata_e)['hex']
 168          txmodsize_e = get_modified_size(node, txdata_e)
 169          txid_e = node.sendrawtransaction(txdata_e)
 170          self.sync_all(self.nodes[:2])
 171          self.sync_all(self.nodes[2:])
 172  
 173          txid_e_starting_prio = (((amt_c - fee) * BTC) + (amt_c2 * BTC * 2)) / txmodsize_e
 174          self.assert_prio(txid_e, txid_e_starting_prio, txid_e_starting_prio)  # Sanity check 1
 175  
 176          self.generate(competing_miner, 5, sync_fun=self.no_op)
 177          self.sync_all(self.nodes[:2])
 178          self.sync_all(self.nodes[2:])
 179  
 180          self.assert_prio(txid_e, txid_e_starting_prio, txid_e_starting_prio)  # Sanity check 2
 181  
 182          self.testmsg('priority is updated correctly when input-confirming block is reorganised out')
 183  
 184          self.connect_nodes(1, 2)
 185          self.sync_blocks()
 186  
 187          txid_e_reorg_prio = (amt_c2 * BTC * 6) / txmodsize_e
 188          self.assert_prio(txid_e, txid_e_starting_prio, txid_e_reorg_prio)
 189  
 190  if __name__ == '__main__':
 191      PriorityTest(__file__).main()
 192