Skip to content

Commit 069415f

Browse files
committed
Test: Add test for coin-age priority mining
1 parent 1dddd69 commit 069415f

File tree

2 files changed

+201
-0
lines changed

2 files changed

+201
-0
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2016 The Bitcoin Core 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 BitcoinTestFramework
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(BitcoinTestFramework):
44+
45+
def set_test_params(self):
46+
self.num_nodes = 3
47+
self.testmsg_num = 0
48+
49+
def add_options(self, parser):
50+
self.add_wallet_options(parser)
51+
parser.add_argument("--gbt", dest="test_gbt", default=False, action="store_true",
52+
help="Test priorities used by GBT")
53+
54+
def setup_nodes(self):
55+
ppopt = []
56+
if self.options.test_gbt:
57+
ppopt.append('-printpriority')
58+
59+
self.extra_args = [
60+
['-blockmaxsize=0'],
61+
['-blockprioritysize=1000000', '-blockmaxsize=1000000'] + ppopt,
62+
['-blockmaxsize=0'],
63+
]
64+
65+
super().setup_nodes()
66+
67+
def assert_prio(self, txid, starting, current):
68+
node = self.nodes[1]
69+
70+
if self.options.test_gbt:
71+
tmpl = node.getblocktemplate({'rules':('segwit',)})
72+
tmplentry = None
73+
for tx in tmpl['transactions']:
74+
if tx['txid'] == txid:
75+
tmplentry = tx
76+
break
77+
# GBT does not expose starting priority, so we don't check that
78+
assert_approximate(tmplentry['priority'], current)
79+
else:
80+
mempoolentry = node.getrawmempool(True)[txid]
81+
assert_approximate(mempoolentry['startingpriority'], starting)
82+
assert_approximate(mempoolentry['currentpriority'], current)
83+
84+
def testmsg(self, msg):
85+
self.testmsg_num += 1
86+
self.log.info('Test %d: %s' % (self.testmsg_num, msg))
87+
88+
def skip_test_if_missing_module(self):
89+
self.skip_if_no_wallet()
90+
91+
def run_test(self):
92+
node = self.nodes[0]
93+
miner = self.nodes[1]
94+
95+
self.generate(node, 50)
96+
self.generate(miner, 101)
97+
98+
fee = Decimal('0.0001')
99+
amt = Decimal('11')
100+
101+
txid_a = node.sendtoaddress(node.getnewaddress(), amt)
102+
txdata_b = node.createrawtransaction([find_unspent(node, txid_a, amt)], {node.getnewaddress(): amt - fee})
103+
txdata_b = node.signrawtransactionwithwallet(txdata_b)['hex']
104+
txmodsize_b = get_modified_size(node, txdata_b)
105+
txid_b = node.sendrawtransaction(txdata_b)
106+
self.sync_all()
107+
108+
self.testmsg('priority starts at 0 with all unconfirmed inputs')
109+
self.assert_prio(txid_b, 0, 0)
110+
111+
self.testmsg('priority increases correctly when that input is mined')
112+
113+
# Mine only the sendtoaddress transaction
114+
tmpl = node.getblocktemplate({'rules':('segwit',)})
115+
rawblock = solve_template_hex(tmpl, [node.getrawtransaction(txid_a)])
116+
assert_equal(node.submitblock(rawblock), None)
117+
self.sync_all()
118+
119+
self.assert_prio(txid_b, 0, amt * BTC / txmodsize_b)
120+
121+
self.testmsg('priority continues to increase the deeper the block confirming its inputs gets buried')
122+
123+
self.generate(node, 2)
124+
125+
self.assert_prio(txid_b, 0, amt * BTC * 3 / txmodsize_b)
126+
127+
self.testmsg('with a confirmed input, the initial priority is calculated correctly')
128+
129+
self.generate(miner, 4)
130+
131+
amt_c = (amt - fee) / 2
132+
amt_c2 = amt_c - fee
133+
txdata_c = node.createrawtransaction([find_unspent(node, txid_b, amt - fee)], {node.getnewaddress(): amt_c, node.getnewaddress(): amt_c2})
134+
txdata_c = node.signrawtransactionwithwallet(txdata_c)['hex']
135+
txmodsize_c = get_modified_size(node, txdata_c)
136+
txid_c = node.sendrawtransaction(txdata_c)
137+
self.sync_all()
138+
139+
txid_c_starting_prio = (amt - fee) * BTC * 4 / txmodsize_c
140+
self.assert_prio(txid_c, txid_c_starting_prio, txid_c_starting_prio)
141+
142+
self.testmsg('with an input confirmed prior to the transaction, the priority gets incremented correctly as it gets buried deeper')
143+
144+
self.generate(node, 1)
145+
146+
self.assert_prio(txid_c, txid_c_starting_prio, (amt - fee) * BTC * 5 / txmodsize_c)
147+
148+
self.testmsg('with an input confirmed prior to the transaction, the priority gets incremented correctly as it gets buried deeper and deeper')
149+
150+
self.generate(node, 2)
151+
152+
self.assert_prio(txid_c, txid_c_starting_prio, (amt - fee) * BTC * 7 / txmodsize_c)
153+
154+
self.log.info('(preparing for reorg test)')
155+
156+
self.generate(miner, 1)
157+
158+
self.split_network()
159+
node = self.nodes[0]
160+
miner = self.nodes[1]
161+
competing_miner = self.nodes[2]
162+
163+
txdata_d = node.createrawtransaction([find_unspent(node, txid_c, amt_c)], {node.getnewaddress(): amt_c - fee})
164+
txdata_d = node.signrawtransactionwithwallet(txdata_d)['hex']
165+
get_modified_size(node, txdata_d)
166+
txid_d = node.sendrawtransaction(txdata_d)
167+
self.sync_all(self.nodes[:2])
168+
self.sync_all(self.nodes[2:])
169+
170+
self.generate(miner, 1, sync_fun=self.no_op)
171+
self.sync_all(self.nodes[:2])
172+
self.sync_all(self.nodes[2:])
173+
174+
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})
175+
txdata_e = node.signrawtransactionwithwallet(txdata_e)['hex']
176+
txmodsize_e = get_modified_size(node, txdata_e)
177+
txid_e = node.sendrawtransaction(txdata_e)
178+
self.sync_all(self.nodes[:2])
179+
self.sync_all(self.nodes[2:])
180+
181+
txid_e_starting_prio = (((amt_c - fee) * BTC) + (amt_c2 * BTC * 2)) / txmodsize_e
182+
self.assert_prio(txid_e, txid_e_starting_prio, txid_e_starting_prio) # Sanity check 1
183+
184+
self.generate(competing_miner, 5, sync_fun=self.no_op)
185+
self.sync_all(self.nodes[:2])
186+
self.sync_all(self.nodes[2:])
187+
188+
self.assert_prio(txid_e, txid_e_starting_prio, txid_e_starting_prio) # Sanity check 2
189+
190+
self.testmsg('priority is updated correctly when input-confirming block is reorganised out')
191+
192+
self.connect_nodes(1, 2)
193+
self.sync_blocks()
194+
195+
txid_e_reorg_prio = (amt_c2 * BTC * 6) / txmodsize_e
196+
self.assert_prio(txid_e, txid_e_starting_prio, txid_e_reorg_prio)
197+
198+
if __name__ == '__main__':
199+
PriorityTest(__file__).main()

test/functional/test_runner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@
192192
'tool_signet_miner.py --descriptors',
193193
'wallet_txn_clone.py',
194194
'wallet_txn_clone.py --segwit',
195+
'mining_coin_age_priority.py',
196+
'mining_coin_age_priority.py --gbt',
195197
'rpc_getchaintips.py',
196198
'rpc_misc.py',
197199
'p2p_1p1c_network.py',

0 commit comments

Comments
 (0)