Skip to content

Commit bcd8db9

Browse files
committed
functional test for pak feature
1 parent 5b6d767 commit bcd8db9

File tree

3 files changed

+300
-2
lines changed

3 files changed

+300
-2
lines changed

test/functional/feature_pak.py

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
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+
from test_framework.test_framework import BitcoinTestFramework
6+
from test_framework.util import assert_equal, assert_raises_rpc_error, connect_nodes_bi, sync_blocks, Decimal
7+
import copy
8+
import time
9+
10+
def pak_to_option(pak):
11+
return list(map(lambda x: "-pak=%s:%s" % (x[0], x[1]), pak))
12+
13+
# This tests a PAK list transition from the genesis state ('reject') to pak1 to
14+
# 'reject' and finally to pak2. There are 5 nodes each with different
15+
# configurations
16+
# All nodes validate pegouts but the first one
17+
18+
# The node at index 0 doesn't validate pegouts, just normal standardness
19+
i_novalidate = 0
20+
# The node at index 1 has no paklist in config
21+
i_undefined = 1
22+
# Paklist 1 in config
23+
i_pak1 = 2
24+
# Paklist 2 in config
25+
i_pak2 = 3
26+
# Reject in config
27+
i_reject = 4
28+
29+
# The two conflicting pak lists
30+
pak1 = [("02fcba7ecf41bc7e1be4ee122d9d22e3333671eb0a3a87b5cdf099d59874e1940f", "02a28b3078b6fe9d2b0f098ffb491b8e98a7fe56ebe321ba52f90becdd06507bbf"),
31+
("02101bed11081c19b25e02dd618da53af1ba29849bbe4006fb3d6e2d3b0d874405", "02c9cf4bdef23d38e6c9ae73b83001711debea113573cfbe0fb729ff81638549da")]
32+
pak2 = [("03767a74373b7207c5ae1214295197a88ec2abdf92e9e2a29daf024c322fae9fcb", "033e4740d0ba639e28963f3476157b7cf2fb7c6fdf4254f97099cf8670b505ea59"),
33+
("02f4a7445f9c48ee8590a930d3fc4f0f5763e3d1d003fdf5fc822e7ba18f380632", "036b3786f029751ada9f02f519a86c7e02fb2963a7013e7e668eb5f7ec069b9e7e")]
34+
35+
# Args that will be re-used in slightly different ways across runs
36+
args = [["-acceptnonstdtxn=0", "-initialfreecoins=100000000"]] \
37+
+ [["-acceptnonstdtxn=0", "-enforce_pak=1", "-initialfreecoins=100000000"]]*4
38+
args[i_reject] = args[i_reject] + ['-pak=reject']
39+
# Novalidate has pak entry, should not act on it ever
40+
args[i_novalidate] = args[i_novalidate] + pak_to_option(pak1)
41+
42+
class PAKTest (BitcoinTestFramework):
43+
44+
def set_test_params(self):
45+
self.num_nodes = 5
46+
self.setup_clean_chain = True
47+
self.extra_args = copy.deepcopy(args)
48+
self.extra_args[i_pak1] = self.extra_args[i_pak1] + pak_to_option(pak1)
49+
self.extra_args[i_pak2] = self.extra_args[i_pak2] + pak_to_option(pak2)
50+
51+
def run_test(self):
52+
53+
# Give novalidate 50 BTC
54+
self.nodes[i_novalidate].generate(101)
55+
self.sync_all()
56+
57+
# This function tests the result of the getpakinfo RPC.
58+
# *_pak is either False (undefined paklist), "reject" or a list of
59+
# (online, offline) tuples
60+
def test_pak(node, config_pak, block_pak, validate):
61+
getpakinfo = node.getpakinfo()
62+
63+
def compare(actual, expected):
64+
if expected is False:
65+
assert_equal(actual, {})
66+
elif "reject" in expected:
67+
assert_equal(actual['offline'], [])
68+
assert_equal(actual['online'], [])
69+
assert_equal(actual['reject'], True)
70+
else:
71+
offline = list(map(lambda x: x[0], expected))
72+
online = list(map(lambda x: x[1], expected))
73+
assert_equal(actual['offline'], offline)
74+
assert_equal(actual['online'], online)
75+
assert_equal(actual['reject'], False)
76+
compare(getpakinfo['config_paklist'], config_pak)
77+
compare(getpakinfo['block_paklist'], block_pak)
78+
79+
# In the beginning the blockchain paklist is "reject"
80+
test_pak(self.nodes[i_novalidate], pak1, "reject", False)
81+
test_pak(self.nodes[i_undefined], False, "reject", True)
82+
test_pak(self.nodes[i_pak1], pak1, "reject", True)
83+
test_pak(self.nodes[i_pak2], pak2, "reject", True)
84+
test_pak(self.nodes[i_reject], "reject", "reject", True)
85+
86+
# i_novalidate creates block without a commitment
87+
block_proposal = self.nodes[i_novalidate].getnewblockhex()
88+
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
89+
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
90+
91+
assert_raises_rpc_error(-25, "Proposal does not have required PAK commitment.", self.nodes[i_pak1].testproposedblock, block_proposal)
92+
93+
# i_undefined creates a block without a commitment
94+
block_proposal = self.nodes[i_undefined].getnewblockhex()
95+
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
96+
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
97+
98+
assert_raises_rpc_error(-25, "Proposal does not have required PAK commitment.", self.nodes[i_pak1].testproposedblock, block_proposal)
99+
100+
101+
# PAK transition: reject -> pak1
102+
# Create a new block with node i_pak1. Because it contains a commitment
103+
# to pak1 it should be rejected by i_pak2 and i_reject.
104+
block_proposal = self.nodes[i_pak1].getnewblockhex()
105+
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
106+
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
107+
assert_equal(self.nodes[i_pak1].testproposedblock(block_proposal), None)
108+
109+
assert_raises_rpc_error(-25, "Proposal PAK commitment and config PAK do not match.", self.nodes[i_pak2].testproposedblock, block_proposal)
110+
111+
assert_raises_rpc_error(-25, "Proposal PAK commitment and config PAK do not match.", self.nodes[i_reject].testproposedblock, block_proposal)
112+
113+
# Submit block with commitment to pak1 and check each node's state.
114+
self.nodes[i_undefined].submitblock(block_proposal)
115+
self.sync_all()
116+
test_pak(self.nodes[i_novalidate], pak1, pak1, False)
117+
test_pak(self.nodes[i_undefined], False, pak1, True)
118+
test_pak(self.nodes[i_pak1], pak1, pak1, True)
119+
test_pak(self.nodes[i_pak2], pak2, pak1, True)
120+
test_pak(self.nodes[i_reject], "reject", pak1, True)
121+
# Check that another block by i_pak1 (without a commitment) is valid to
122+
# i_pak1 but invalid to i_pak2 and i_reject
123+
block_proposal = self.nodes[i_undefined].getnewblockhex()
124+
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
125+
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
126+
assert_equal(self.nodes[i_pak1].testproposedblock(block_proposal), None)
127+
128+
assert_raises_rpc_error(-25, "Proposal does not have required PAK commitment.", self.nodes[i_pak2].testproposedblock, block_proposal)
129+
130+
assert_raises_rpc_error(-25, "Proposal does not have required PAK commitment.", self.nodes[i_reject].testproposedblock, block_proposal)
131+
132+
# PAK transition: pak1 -> reject
133+
# Create a new block with i_reject which should have a "reject" commitment
134+
# and check that it's correctly rejected or accepted.
135+
block_proposal = self.nodes[i_reject].getnewblockhex()
136+
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
137+
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
138+
139+
assert_raises_rpc_error(-25, "Proposal PAK commitment and config PAK do not match.", self.nodes[i_pak1].testproposedblock, block_proposal)
140+
141+
assert_equal(self.nodes[i_reject].testproposedblock(block_proposal), None)
142+
# Submit "reject" block and check state.
143+
self.nodes[i_undefined].submitblock(block_proposal)
144+
self.sync_all()
145+
test_pak(self.nodes[i_novalidate], pak1, "reject", False)
146+
test_pak(self.nodes[i_undefined], False, "reject", True)
147+
test_pak(self.nodes[i_pak1], pak1, "reject", True)
148+
test_pak(self.nodes[i_pak2], pak2, "reject", True)
149+
test_pak(self.nodes[i_reject], "reject", "reject", True)
150+
# Check that another block by i_reject (without a commitment) is valid to i_reject.
151+
block_proposal = self.nodes[i_reject].getnewblockhex()
152+
assert_equal(self.nodes[i_reject].testproposedblock(block_proposal), None)
153+
154+
# Check that i_undefined can't peg-out because of the pegout freeze.
155+
assert_raises_rpc_error(-5, "Pegout freeze is under effect", self.nodes[i_undefined].sendtomainchain, "", 1)
156+
157+
assert_raises_rpc_error(-3, "`address` argument must be \"\" for PAK-enabled networks as the address is generated automatically.", self.nodes[i_undefined].sendtomainchain, "n3NkSZqoPMCQN5FENxUBw4qVATbytH6FDK", 1)
158+
159+
# PAK transition: reject -> pak2
160+
# Restart nodes while putting pak2 in i_pak1's config instead of pak1.
161+
self.stop_nodes()
162+
extra_args = copy.deepcopy(args)
163+
extra_args[i_pak1] = extra_args[i_pak1] + pak_to_option(pak2)
164+
extra_args[i_pak2] = extra_args[i_pak2] + pak_to_option(pak2)
165+
# Also test novalidate behaves correctly when set to reject after removing
166+
# the two pak entries
167+
extra_args[i_novalidate] = extra_args[i_novalidate][:-2] + ['-pak=reject']
168+
169+
# Restart and connect peers
170+
self.start_nodes(extra_args)
171+
connect_nodes_bi(self.nodes,0,1)
172+
connect_nodes_bi(self.nodes,1,2)
173+
connect_nodes_bi(self.nodes,2,3)
174+
connect_nodes_bi(self.nodes,3,4)
175+
176+
# Check current state of i_pak1
177+
test_pak(self.nodes[i_pak1], pak2, "reject", True)
178+
# Create a new block with i_pak1 which should have a commitment to pak2
179+
# and check that it's correctly rejected or accepted.
180+
block_proposal = self.nodes[i_pak1].getnewblockhex()
181+
assert_equal(self.nodes[i_novalidate].testproposedblock(block_proposal), None)
182+
assert_equal(self.nodes[i_undefined].testproposedblock(block_proposal), None)
183+
assert_equal(self.nodes[i_pak1].testproposedblock(block_proposal), None)
184+
assert_equal(self.nodes[i_pak2].testproposedblock(block_proposal), None)
185+
186+
assert_raises_rpc_error(-25, "Proposal PAK commitment and config PAK do not match.", self.nodes[i_reject].testproposedblock, block_proposal)
187+
188+
# Submit block with commitment to pak2 and check state.
189+
self.nodes[i_pak1].submitblock(block_proposal)
190+
self.sync_all()
191+
test_pak(self.nodes[i_novalidate], "reject", pak2, False)
192+
test_pak(self.nodes[i_undefined], False, pak2, True)
193+
test_pak(self.nodes[i_pak1], pak2, pak2, True)
194+
test_pak(self.nodes[i_pak2], pak2, pak2, True)
195+
test_pak(self.nodes[i_reject], "reject", pak2, True)
196+
197+
# Reset PAK conf arguments to start to test mempool acceptance and wallet
198+
199+
# We will re-use the same xpub, but each wallet will create its own online pak
200+
# so the lists will be incompatible, even if all else was synced
201+
xpub = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B"
202+
init_results = []
203+
info_results = []
204+
for i in range(5):
205+
if i == 0:
206+
assert_raises_rpc_error(-8, "PAK enforcement is not enabled on this network.", self.nodes[i].initpegoutwallet, xpub)
207+
init_results += [None]
208+
info_results += [None]
209+
continue
210+
211+
init_results += [ self.nodes[i].initpegoutwallet(xpub) ]
212+
info_results += [ self.nodes[i].getwalletpakinfo() ]
213+
assert_equal(init_results[i]["address_lookahead"], info_results[i]["address_lookahead"])
214+
assert_equal(init_results[i]["liquid_pak"], info_results[i]["liquid_pak"])
215+
assert_equal(init_results[i]["liquid_pak_address"], info_results[i]["liquid_pak_address"])
216+
assert_equal(info_results[i]["bitcoin_xpub"], xpub)
217+
assert_equal(info_results[i]["derivation_path"], "/0/0")
218+
219+
# Use custom derivation counter values, check if stored correctly,
220+
# address lookahead looks correct and that new liquid_pak was chosen
221+
assert_raises_rpc_error(-8, "bip32_counter must be between 0 and 1,000,000,000, inclusive.", self.nodes[i_undefined].initpegoutwallet, xpub, -1)
222+
223+
224+
assert_raises_rpc_error(-8, "bip32_counter must be between 0 and 1,000,000,000, inclusive.", self.nodes[i_undefined].initpegoutwallet, xpub, 1000000001)
225+
226+
new_init = self.nodes[i_undefined].initpegoutwallet(xpub, 2)
227+
assert_equal(self.nodes[i_undefined].getwalletpakinfo()["derivation_path"], "/0/2")
228+
assert_equal(new_init["address_lookahead"][0], init_results[i_undefined]["address_lookahead"][2])
229+
assert(new_init["liquid_pak"] != init_results[i_undefined]["liquid_pak"])
230+
231+
# Load additional pak entry for each, restart (reject node disallows pak list in conf)
232+
# By adding different pak entries, all nodes that validate the list should conflict
233+
self.stop_nodes()
234+
extra_args = copy.deepcopy(args)
235+
extra_args[i_pak1] = extra_args[i_pak1]+["-"+init_results[i_pak1]["pakentry"]]
236+
extra_args[i_pak2] = extra_args[i_pak2]+["-"+init_results[i_pak2]["pakentry"]]
237+
238+
# Restart and connect peers
239+
self.start_nodes(extra_args)
240+
connect_nodes_bi(self.nodes,0,1)
241+
connect_nodes_bi(self.nodes,1,2)
242+
connect_nodes_bi(self.nodes,2,3)
243+
connect_nodes_bi(self.nodes,3,4)
244+
245+
# Check PAK settings persistance in wallet across restart
246+
restarted_info = self.nodes[i_undefined].getwalletpakinfo()
247+
assert_equal(restarted_info["bitcoin_xpub"], xpub)
248+
assert_equal(restarted_info["liquid_pak"], new_init["liquid_pak"])
249+
assert_equal(restarted_info["derivation_path"], "/0/2")
250+
251+
# Have nodes send pegouts, check it fails to enter mempool of other nodes with incompatible
252+
# PAK settings
253+
self.nodes[i_novalidate].sendmany("", {self.nodes[i_undefined].getnewaddress():10, self.nodes[i_pak1].getnewaddress():10, self.nodes[i_pak2].getnewaddress():10, self.nodes[i_reject].getnewaddress():10})
254+
self.nodes[i_novalidate].generate(1)
255+
self.sync_all()
256+
257+
# pak1 generates a block, creating block commitment
258+
self.nodes[i_pak1].generate(1)
259+
self.sync_all()
260+
261+
# pak1 will now create a pegout.
262+
pak1_pegout_txid = self.nodes[i_pak1].sendtomainchain("", 1)["txid"]
263+
assert_equal(self.nodes[i_pak1].getwalletpakinfo()["derivation_path"], "/0/1")
264+
265+
266+
# Wait for two nodes to get transaction in mempool only
267+
time_to_wait = 15
268+
while time_to_wait > 0:
269+
# novalidate doesn't allow >80 byte op_return outputs due to no enforce_pak
270+
if (pak1_pegout_txid not in self.nodes[i_novalidate].getrawmempool() and
271+
pak1_pegout_txid in self.nodes[i_undefined].getrawmempool() and
272+
pak1_pegout_txid not in self.nodes[i_pak2].getrawmempool() and
273+
pak1_pegout_txid not in self.nodes[i_reject].getrawmempool()):
274+
break
275+
time_to_wait -= 1
276+
time.sleep(1)
277+
assert(time_to_wait > 0)
278+
279+
# pak_reject will make a block commitment, causing all validating nodes to dump
280+
# the peg transaction
281+
self.nodes[i_reject].generate(1)
282+
sync_blocks(self.nodes)
283+
284+
assert_equal(pak1_pegout_txid in self.nodes[i_novalidate].getrawmempool(), False)
285+
assert_equal(pak1_pegout_txid in self.nodes[i_undefined].getrawmempool(), False)
286+
assert_equal(pak1_pegout_txid in self.nodes[i_pak2].getrawmempool(), False)
287+
assert_equal(pak1_pegout_txid in self.nodes[i_reject].getrawmempool(), False)
288+
289+
# Fail to peg-out too-small value
290+
assert_raises_rpc_error(-8, "Invalid amount for send, must send more than 0.0001 BTC", self.nodes[i_undefined].sendtomainchain, "", Decimal('0.0009'))
291+
292+
# Use wrong network's extended pubkey
293+
mainnetxpub = "xpub6AATBi58516uxLogbuaG3jkom7x1qyDoZzMN2AePBuQnMFKUV9xC2BW9vXsFJ9rELsvbeGQcFWhtbyM4qDeijM22u3AaSiSYEvuMZkJqtLn"
294+
assert_raises_rpc_error(-8, "bitcoin_xpub is invalid for this network", self.nodes[i_undefined].initpegoutwallet, mainnetxpub)
295+
296+
if __name__ == '__main__':
297+
PAKTest ().main ()

test/functional/mempool_accept.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ def run_test(self):
266266
tx.vout = [tx.vout[0]] * 2
267267
self.check_mempool_result(
268268
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': '66: min relay fee not met'}],
269-
rawtxs=[bytes_to_hex_str(tx.serialize())],
269+
rawtxs=[bytes_to_hex_str(tx.serialize())],
270270
)
271271

272272
self.log.info('A timelocked transaction')

test/functional/test_runner.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@
172172
'feature_mandatory_coinbase.py',
173173
'feature_block_subsidy.py',
174174
'feature_connect_coinbase.py',
175-
'feature_block_v4.py'
175+
'feature_block_v4.py',
176+
'feature_pak.py'
176177
# Don't append tests at the end to avoid merge conflicts
177178
# Put them in a random line within the section that fits their approximate run-time
178179
]

0 commit comments

Comments
 (0)