Skip to content

Commit 2e077cc

Browse files
committed
QA: Test 2wp using the test framework
1 parent 3c6d2df commit 2e077cc

2 files changed

Lines changed: 338 additions & 0 deletions

File tree

qa/pull-tester/rpc-tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
raise
100100

101101
testScripts = [
102+
'feature_fedpeg.py',
102103
# longest test should go first, to favor running tests in parallel
103104
'wallet-hd.py',
104105
#'walletbackup.py',

qa/rpc-tests/feature_fedpeg.py

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
#!/usr/bin/env python3
2+
3+
from decimal import Decimal
4+
import time
5+
6+
from test_framework.authproxy import JSONRPCException
7+
from test_framework.test_framework import BitcoinTestFramework
8+
from test_framework.util import (
9+
connect_nodes_bi,
10+
rpc_auth_pair,
11+
rpc_port,
12+
start_node,
13+
start_nodes,
14+
)
15+
16+
# Sync mempool, make a block, sync blocks
17+
def sync_all(sidechain, sidechain2, makeblock=True):
18+
block = ""
19+
timeout = 20
20+
while len(sidechain.getrawmempool()) != len(sidechain2.getrawmempool()):
21+
time.sleep(1)
22+
timeout -= 1
23+
if timeout == 0:
24+
raise Exception("Peg-in has failed to propagate.")
25+
if makeblock:
26+
block = sidechain2.generate(1)
27+
while sidechain.getblockcount() != sidechain2.getblockcount():
28+
time.sleep(1)
29+
timeout -= 1
30+
if timeout == 0:
31+
raise Exception("Blocks are not propagating.")
32+
return block
33+
34+
def get_new_unconfidential_address(node):
35+
addr = node.getnewaddress()
36+
val_addr = node.validateaddress(addr)
37+
if 'unconfidential' in val_addr:
38+
return val_addr['unconfidential']
39+
return val_addr['address']
40+
41+
class FedPegTest(BitcoinTestFramework):
42+
43+
def __init__(self):
44+
super().__init__()
45+
self.setup_clean_chain = True
46+
self.num_nodes = 4
47+
48+
def setup_network(self, split=False):
49+
50+
# Parent chain args
51+
self.extra_args = [[
52+
# '-printtoconsole',
53+
'-validatepegin=0',
54+
'-anyonecanspendaremine',
55+
'-initialfreecoins=2100000000000000',
56+
]] * 2
57+
58+
self.nodes = start_nodes(2, self.options.tmpdir, self.extra_args[:2], chain='parent')
59+
connect_nodes_bi(self.nodes, 0, 1)
60+
self.parentgenesisblockhash = self.nodes[0].getblockhash(0)
61+
print('parentgenesisblockhash', self.parentgenesisblockhash)
62+
63+
# Sidechain args
64+
for n in range(2):
65+
rpc_u, rpc_p = rpc_auth_pair(n)
66+
self.extra_args.append([
67+
# '-printtoconsole',
68+
'-parentgenesisblockhash=%s' % self.parentgenesisblockhash,
69+
'-validatepegin=1',
70+
'-anyonecanspendaremine=0',
71+
'-initialfreecoins=0',
72+
'-peginconfirmationdepth=10',
73+
'-mainchainrpchost=127.0.0.1',
74+
'-mainchainrpcport=%s' % rpc_port(n),
75+
'-mainchainrpcuser=%s' % rpc_u,
76+
'-mainchainrpcpassword=%s' % rpc_p,
77+
'-parentpubkeyprefix=235',
78+
'-parentscriptprefix=75',
79+
'-con_parent_chain_has_pow=0',
80+
])
81+
self.nodes.append(start_node(n + 2, self.options.tmpdir, self.extra_args[n + 2], chain='sidechain'))
82+
83+
connect_nodes_bi(self.nodes, 2, 3)
84+
self.is_network_split = True
85+
self.sync_all()
86+
87+
def test_pegout(self, parent_chain_addr, sidechain):
88+
pegout_txid = sidechain.sendtomainchain(parent_chain_addr, 1)
89+
raw_pegout = sidechain.getrawtransaction(pegout_txid, True)
90+
assert 'vout' in raw_pegout and len(raw_pegout['vout']) > 0
91+
pegout_tested = False
92+
for output in raw_pegout['vout']:
93+
scriptPubKey = output['scriptPubKey']
94+
if 'type' in scriptPubKey and scriptPubKey['type'] == 'nulldata':
95+
assert ('pegout_hex' in scriptPubKey and 'pegout_asm' in scriptPubKey and 'pegout_type' in scriptPubKey and
96+
'pegout_chain' in scriptPubKey and 'pegout_reqSigs' in scriptPubKey and 'pegout_addresses' in scriptPubKey)
97+
assert scriptPubKey['pegout_chain'] == self.parentgenesisblockhash
98+
assert scriptPubKey['pegout_reqSigs'] == 1
99+
assert parent_chain_addr in scriptPubKey['pegout_addresses']
100+
pegout_tested = True
101+
break
102+
assert pegout_tested
103+
104+
def run_test(self):
105+
parent = self.nodes[0]
106+
parent2 = self.nodes[1]
107+
sidechain = self.nodes[2]
108+
sidechain2 = self.nodes[3]
109+
110+
parent.generate(101)
111+
sidechain.generate(101)
112+
113+
addrs = sidechain.getpeginaddress()
114+
addr = parent.validateaddress(addrs["mainchain_address"])
115+
print('addrs', addrs)
116+
print('addr', addr)
117+
txid1 = parent.sendtoaddress(addrs["mainchain_address"], 24)
118+
# 10+2 confirms required to get into mempool and confirm
119+
parent.generate(1)
120+
time.sleep(2)
121+
proof = parent.gettxoutproof([txid1])
122+
raw = parent.getrawtransaction(txid1)
123+
import json
124+
print('raw', parent.getrawtransaction(txid1, True))
125+
126+
print("Attempting peg-in")
127+
# First attempt fails the consensus check but gives useful result
128+
try:
129+
pegtxid = sidechain.claimpegin(raw, proof)
130+
raise Exception("Peg-in should not be mature enough yet, need another block.")
131+
except JSONRPCException as e:
132+
assert("Peg-in Bitcoin transaction needs more confirmations to be sent." in e.error["message"])
133+
134+
# Second attempt simply doesn't hit mempool bar
135+
parent.generate(10)
136+
try:
137+
pegtxid = sidechain.claimpegin(raw, proof)
138+
raise Exception("Peg-in should not be mature enough yet, need another block.")
139+
except JSONRPCException as e:
140+
assert("Peg-in Bitcoin transaction needs more confirmations to be sent." in e.error["message"])
141+
142+
# Should fail due to non-witness
143+
try:
144+
pegtxid = sidechain.claimpegin(raw, proof, get_new_unconfidential_address(parent))
145+
raise Exception("Peg-in with non-matching claim_script should fail.")
146+
except JSONRPCException as e:
147+
print(e.error["message"])
148+
assert("Given or recovered script is not a witness program." in e.error["message"])
149+
150+
# # Should fail due to non-matching wallet address
151+
# try:
152+
# pegtxid = sidechain.claimpegin(raw, proof, get_new_unconfidential_address(sidechain))
153+
# raise Exception("Peg-in with non-matching claim_script should fail.")
154+
# except JSONRPCException as e:
155+
# print(e.error["message"])
156+
# assert("Given claim_script does not match the given Bitcoin transaction." in e.error["message"])
157+
158+
# 12 confirms allows in mempool
159+
parent.generate(1)
160+
# Should succeed via wallet lookup for address match, and when given
161+
pegtxid1 = sidechain.claimpegin(raw, proof)
162+
163+
# Will invalidate the block that confirms this transaction later
164+
sync_all(parent, parent2)
165+
blockhash = sync_all(sidechain, sidechain2)
166+
sidechain.generate(5)
167+
168+
tx1 = sidechain.gettransaction(pegtxid1)
169+
170+
print('tx1', tx1)
171+
if "confirmations" in tx1 and tx1["confirmations"] == 6:
172+
print("Peg-in is confirmed: Success!")
173+
else:
174+
raise Exception("Peg-in confirmation has failed.")
175+
176+
# Look at pegin fields
177+
decoded = sidechain.decoderawtransaction(tx1["hex"])
178+
assert decoded["vin"][0]["is_pegin"] == True
179+
assert len(decoded["vin"][0]["pegin_witness"]) > 0
180+
# Check that there's sufficient fee for the peg-in
181+
vsize = decoded["vsize"]
182+
fee_output = decoded["vout"][1]
183+
fallbackfee_pervbyte = Decimal("0.00001")/Decimal("1000")
184+
assert fee_output["scriptPubKey"]["type"] == "fee"
185+
assert fee_output["value"] >= fallbackfee_pervbyte*vsize
186+
187+
# Quick reorg checks of pegs
188+
sidechain.invalidateblock(blockhash[0])
189+
if sidechain.gettransaction(pegtxid1)["confirmations"] != 0:
190+
raise Exception("Peg-in didn't unconfirm after invalidateblock call.")
191+
# Re-enters block
192+
sidechain.generate(1)
193+
if sidechain.gettransaction(pegtxid1)["confirmations"] != 1:
194+
raise Exception("Peg-in should have one confirm on side block.")
195+
sidechain.reconsiderblock(blockhash[0])
196+
if sidechain.gettransaction(pegtxid1)["confirmations"] != 6:
197+
raise Exception("Peg-in should be back to 6 confirms.")
198+
199+
# Do many claims in mempool
200+
n_claims = 5
201+
202+
print("Flooding mempool with many small claims")
203+
pegtxs = []
204+
sidechain.generate(101)
205+
206+
for i in range(n_claims):
207+
addrs = sidechain.getpeginaddress()
208+
txid = parent.sendtoaddress(addrs["mainchain_address"], 1)
209+
parent.generate(12)
210+
proof = parent.gettxoutproof([txid])
211+
raw = parent.getrawtransaction(txid)
212+
pegtxs += [sidechain.claimpegin(raw, proof)]
213+
214+
sync_all(parent, parent2)
215+
sync_all(sidechain, sidechain2)
216+
217+
sidechain2.generate(1)
218+
for pegtxid in pegtxs:
219+
tx = sidechain.gettransaction(pegtxid)
220+
if "confirmations" not in tx or tx["confirmations"] == 0:
221+
raise Exception("Peg-in confirmation has failed.")
222+
223+
print("Test pegout")
224+
self.test_pegout(get_new_unconfidential_address(parent), sidechain)
225+
226+
print("Test pegout P2SH")
227+
parent_chain_addr = get_new_unconfidential_address(parent)
228+
parent_pubkey = parent.validateaddress(parent_chain_addr)["pubkey"]
229+
parent_chain_p2sh_addr = parent.createmultisig(1, [parent_pubkey])["address"]
230+
self.test_pegout(parent_chain_p2sh_addr, sidechain)
231+
232+
print("Test pegout Garbage")
233+
parent_chain_addr = "garbage"
234+
try:
235+
self.test_pegout(parent_chain_addr, sidechain)
236+
raise Exception("A garbage address should fail.")
237+
except JSONRPCException as e:
238+
assert("Invalid Bitcoin address" in e.error["message"])
239+
240+
print("Test pegout Garbage valid")
241+
prev_txid = sidechain.sendtoaddress(sidechain.getnewaddress(), 1)
242+
sidechain.generate(1)
243+
pegout_chain = 'a' * 64
244+
pegout_hex = 'b' * 500
245+
inputs = [{"txid": prev_txid, "vout": 0}]
246+
outputs = {"vdata": [pegout_chain, pegout_hex]}
247+
rawtx = sidechain.createrawtransaction(inputs, outputs)
248+
raw_pegout = sidechain.decoderawtransaction(rawtx)
249+
250+
assert 'vout' in raw_pegout and len(raw_pegout['vout']) > 0
251+
pegout_tested = False
252+
for output in raw_pegout['vout']:
253+
scriptPubKey = output['scriptPubKey']
254+
if 'type' in scriptPubKey and scriptPubKey['type'] == 'nulldata':
255+
assert ('pegout_hex' in scriptPubKey and 'pegout_asm' in scriptPubKey and 'pegout_type' in scriptPubKey and
256+
'pegout_chain' in scriptPubKey and 'pegout_reqSigs' not in scriptPubKey and 'pegout_addresses' not in scriptPubKey)
257+
assert scriptPubKey['pegout_type'] == 'nonstandard'
258+
assert scriptPubKey['pegout_chain'] == pegout_chain
259+
assert scriptPubKey['pegout_hex'] == pegout_hex
260+
pegout_tested = True
261+
break
262+
assert pegout_tested
263+
264+
# print ("Now test failure to validate peg-ins based on intermittant bitcoind rpc failure")
265+
# parent2.stop()
266+
# # give parent2 time to stop
267+
# time.sleep(1)
268+
# txid = parent.sendtoaddress(addrs["mainchain_address"], 1)
269+
# parent.generate(12)
270+
# proof = parent.gettxoutproof([txid])
271+
# raw = parent.getrawtransaction(txid)
272+
# stuck_peg = sidechain.claimpegin(raw, proof)
273+
# sidechain.generate(1)
274+
# print("Waiting to ensure block is being rejected by sidechain2")
275+
# time.sleep(5)
276+
277+
# assert(sidechain.getblockcount() != sidechain2.getblockcount())
278+
279+
# print("Restarting parent2")
280+
# parent2 = None
281+
# self.nodes[1] = None
282+
# self.nodes[1] = start_node(1, self.options.tmpdir, self.extra_args[1], chain='parent')
283+
# parent2 = self.nodes[1]
284+
# time.sleep(5)
285+
286+
# # Don't make a block, race condition when pegin-invalid block
287+
# # is awaiting further validation, nodes reject subsequent blocks
288+
# # even ones they create
289+
# sync_all(sidechain, sidechain2, makeblock=True)
290+
# print("Now send funds out in two stages, partial, and full")
291+
# some_btc_addr = get_new_unconfidential_address(parent)
292+
# bal_1 = sidechain.getwalletinfo()["balance"]["bitcoin"]
293+
# try:
294+
# sidechain.sendtomainchain(some_btc_addr, bal_1 + 1)
295+
# raise Exception("Sending out too much; should have failed")
296+
# except JSONRPCException as e:
297+
# assert("Insufficient funds" in e.error["message"])
298+
299+
# assert(sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1)
300+
# try:
301+
# sidechain.sendtomainchain(some_btc_addr+"b", bal_1 - 1)
302+
# raise Exception("Sending to invalid address; should have failed")
303+
# except JSONRPCException as e:
304+
# assert("Invalid Bitcoin address" in e.error["message"])
305+
306+
# assert(sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1)
307+
# try:
308+
# sidechain.sendtomainchain("1Nro9WkpaKm9axmcfPVp79dAJU1Gx7VmMZ", bal_1 - 1)
309+
# raise Exception("Sending to mainchain address when should have been testnet; should have failed")
310+
# except JSONRPCException as e:
311+
# assert("Invalid Bitcoin address" in e.error["message"])
312+
313+
# assert(sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1)
314+
315+
# peg_out_txid = sidechain.sendtomainchain(some_btc_addr, 1)
316+
317+
# peg_out_details = sidechain.decoderawtransaction(sidechain.getrawtransaction(peg_out_txid))
318+
# # peg-out, change
319+
# assert(len(peg_out_details["vout"]) == 3)
320+
# found_pegout_value = False
321+
# for output in peg_out_details["vout"]:
322+
# if "value" in output and output["value"] == 1:
323+
# found_pegout_value = True
324+
# assert(found_pegout_value)
325+
326+
# bal_2 = sidechain.getwalletinfo()["balance"]["bitcoin"]
327+
# # Make sure balance went down
328+
# assert(bal_2 + 1 < bal_1)
329+
330+
# sidechain.sendtomainchain(some_btc_addr, bal_2, True)
331+
332+
# assert("bitcoin" not in sidechain.getwalletinfo()["balance"])
333+
334+
print('Success!')
335+
336+
if __name__ == '__main__':
337+
FedPegTest().main()

0 commit comments

Comments
 (0)