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