mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
421 lines
18 KiB
Python
Executable file
421 lines
18 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
import time
|
|
|
|
from test_framework.authproxy import JSONRPCException
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import (
|
|
connect_nodes_bi,
|
|
get_auth_cookie,
|
|
get_datadir_path,
|
|
rpc_port,
|
|
p2p_port,
|
|
)
|
|
|
|
def get_new_unconfidential_address(node):
|
|
addr = node.getnewaddress()
|
|
val_addr = node.getaddressinfo(addr)
|
|
if 'unconfidential' in val_addr:
|
|
return val_addr['unconfidential']
|
|
return val_addr['address']
|
|
|
|
class FedPegTest(BitcoinTestFramework):
|
|
|
|
def set_test_params(self):
|
|
self.setup_clean_chain = True
|
|
self.num_nodes = 4
|
|
|
|
def add_options(self, parser):
|
|
parser.add_argument("--parent_binpath", dest="parent_binpath", default="",
|
|
help="Use a different binary for launching nodes")
|
|
parser.add_argument("--parent_bitcoin", dest="parent_bitcoin", default=False, action="store_true",
|
|
help="Parent nodes are Bitcoin")
|
|
|
|
def setup_network(self, split=False):
|
|
if self.options.parent_bitcoin and self.options.parent_binpath == "":
|
|
raise Exception("Can't run with --parent_bitcoin without specifying --parent_binpath")
|
|
|
|
self.nodes = []
|
|
# Setup parent nodes
|
|
parent_chain = "regtest" if self.options.parent_bitcoin else "parent"
|
|
parent_binary = [self.options.parent_binpath] if self.options.parent_binpath != "" else None
|
|
for n in range(2):
|
|
extra_args = [
|
|
"-printtoconsole=0",
|
|
"-port="+str(p2p_port(n)),
|
|
"-rpcport="+str(rpc_port(n))
|
|
]
|
|
if self.options.parent_bitcoin:
|
|
extra_args.extend([
|
|
"-addresstype=legacy", # To make sure bitcoind gives back p2pkh no matter version
|
|
])
|
|
else:
|
|
extra_args.extend([
|
|
"-validatepegin=0",
|
|
"-initialfreecoins=2100000000000000",
|
|
"-anyonecanspendaremine",
|
|
"-signblockscript=51", # OP_TRUE
|
|
])
|
|
|
|
self.add_nodes(1, [extra_args], chain=[parent_chain], binary=parent_binary)
|
|
self.start_node(n)
|
|
print("Node {} started".format(n))
|
|
|
|
connect_nodes_bi(self.nodes, 0, 1)
|
|
self.parentgenesisblockhash = self.nodes[0].getblockhash(0)
|
|
print('parentgenesisblockhash', self.parentgenesisblockhash)
|
|
#TODO(rebase) CA
|
|
#if not self.options.parent_bitcoin:
|
|
# parent_pegged_asset = self.nodes[0].getsidechaininfo()['pegged_asset']
|
|
|
|
# Setup sidechain nodes
|
|
self.fedpeg_script = "512103dff4923d778550cc13ce0d887d737553b4b58f4e8e886507fc39f5e447b2186451ae"
|
|
for n in range(2):
|
|
extra_args = [
|
|
"-printtoconsole=0",
|
|
"-port="+str(p2p_port(2+n)),
|
|
"-rpcport="+str(rpc_port(2+n)),
|
|
'-parentgenesisblockhash=%s' % self.parentgenesisblockhash,
|
|
'-validatepegin=1',
|
|
'-fedpegscript=%s' % self.fedpeg_script,
|
|
'-anyonecanspendaremine=0',
|
|
'-minrelaytxfee=0',
|
|
'-blockmintxfee=0',
|
|
'-initialfreecoins=0',
|
|
'-peginconfirmationdepth=10',
|
|
'-mainchainrpchost=127.0.0.1',
|
|
'-mainchainrpcport=%s' % rpc_port(n),
|
|
'-recheckpeginblockinterval=15', # Long enough to allow failure and repair before timeout
|
|
]
|
|
if not self.options.parent_bitcoin:
|
|
extra_args.extend([
|
|
#TODO(rebase) should the defaults be put back to elements-specific ones?
|
|
#'-parentpubkeyprefix=235',
|
|
#'-parentscriptprefix=75',
|
|
'-parentpubkeyprefix=111',
|
|
'-parentscriptprefix=196',
|
|
'-con_parent_chain_signblockscript=51',
|
|
#TODO(rebase) CA
|
|
#'-con_parent_pegged_asset=%s' % parent_pegged_asset,
|
|
])
|
|
|
|
# Use rpcuser auth only for first parent.
|
|
if n==0:
|
|
# Extract username and password from cookie file and use directly.
|
|
datadir = get_datadir_path(self.options.tmpdir, n)
|
|
rpc_u, rpc_p = get_auth_cookie(datadir, parent_chain)
|
|
extra_args.extend([
|
|
'-mainchainrpcuser=%s' % rpc_u,
|
|
'-mainchainrpcpassword=%s' % rpc_p,
|
|
])
|
|
else:
|
|
# Need to specify where to find parent cookie file
|
|
datadir = get_datadir_path(self.options.tmpdir, n)
|
|
extra_args.append('-mainchainrpccookiefile='+datadir+"/" + parent_chain + "/.cookie")
|
|
|
|
self.add_nodes(1, [extra_args], chain=["sidechain"])
|
|
self.start_node(2+n)
|
|
print("Node {} started".format(2+n))
|
|
|
|
# We only connect the same-chain nodes, so sync_all works correctly
|
|
connect_nodes_bi(self.nodes, 2, 3)
|
|
self.node_groups = [[self.nodes[0], self.nodes[1]], [self.nodes[2], self.nodes[3]]]
|
|
self.sync_all(self.node_groups)
|
|
print("Setting up network done")
|
|
|
|
def test_pegout(self, parent_chain_addr, sidechain):
|
|
pegout_txid = sidechain.sendtomainchain(parent_chain_addr, 1)
|
|
raw_pegout = sidechain.getrawtransaction(pegout_txid, True)
|
|
assert 'vout' in raw_pegout and len(raw_pegout['vout']) > 0
|
|
pegout_tested = False
|
|
for output in raw_pegout['vout']:
|
|
scriptPubKey = output['scriptPubKey']
|
|
if 'type' in scriptPubKey and scriptPubKey['type'] == 'nulldata':
|
|
assert ('pegout_hex' in scriptPubKey and 'pegout_asm' in scriptPubKey and 'pegout_type' in scriptPubKey)
|
|
assert ('pegout_chain' in scriptPubKey and 'pegout_reqSigs' in scriptPubKey and 'pegout_addresses' in scriptPubKey)
|
|
assert scriptPubKey['pegout_chain'] == self.parentgenesisblockhash
|
|
assert scriptPubKey['pegout_reqSigs'] == 1
|
|
assert parent_chain_addr in scriptPubKey['pegout_addresses']
|
|
pegout_tested = True
|
|
break
|
|
assert pegout_tested
|
|
|
|
def run_test(self):
|
|
parent = self.nodes[0]
|
|
#parent2 = self.nodes[1]
|
|
sidechain = self.nodes[2]
|
|
sidechain2 = self.nodes[3]
|
|
|
|
parent.generate(101)
|
|
sidechain.generate(101)
|
|
|
|
addrs = sidechain.getpeginaddress()
|
|
addr = addrs["mainchain_address"]
|
|
print('addrs', addrs)
|
|
print(parent.getaddressinfo(addr))
|
|
txid1 = parent.sendtoaddress(addr, 24)
|
|
# 10+2 confirms required to get into mempool and confirm
|
|
parent.generate(1)
|
|
time.sleep(2)
|
|
proof = parent.gettxoutproof([txid1])
|
|
|
|
raw = parent.getrawtransaction(txid1)
|
|
print('raw', parent.getrawtransaction(txid1, True))
|
|
|
|
print("Attempting peg-in")
|
|
# First attempt fails the consensus check but gives useful result
|
|
try:
|
|
pegtxid = sidechain.claimpegin(raw, proof)
|
|
raise Exception("Peg-in should not be mature enough yet, need another block.")
|
|
except JSONRPCException as e:
|
|
print('RPC ERROR:', e.error['message'])
|
|
assert("Peg-in Bitcoin transaction needs more confirmations to be sent." in e.error["message"])
|
|
|
|
# Second attempt simply doesn't hit mempool bar
|
|
parent.generate(10)
|
|
try:
|
|
pegtxid = sidechain.claimpegin(raw, proof)
|
|
raise Exception("Peg-in should not be mature enough yet, need another block.")
|
|
except JSONRPCException as e:
|
|
print('RPC ERROR:', e.error['message'])
|
|
assert("Peg-in Bitcoin transaction needs more confirmations to be sent." in e.error["message"])
|
|
|
|
try:
|
|
pegtxid = sidechain.createrawpegin(raw, proof, 'AEIOU')
|
|
raise Exception("Peg-in with non-hex claim_script should fail.")
|
|
except JSONRPCException as e:
|
|
print('RPC ERROR:', e.error['message'])
|
|
assert("Given claim_script is not hex." in e.error["message"])
|
|
|
|
# Should fail due to non-matching wallet address
|
|
try:
|
|
scriptpubkey = sidechain.getaddressinfo(get_new_unconfidential_address(sidechain))["scriptPubKey"]
|
|
pegtxid = sidechain.claimpegin(raw, proof, scriptpubkey)
|
|
raise Exception("Peg-in with non-matching claim_script should fail.")
|
|
except JSONRPCException as e:
|
|
print('RPC ERROR:', e.error['message'])
|
|
assert("Given claim_script does not match the given Bitcoin transaction." in e.error["message"])
|
|
|
|
# 12 confirms allows in mempool
|
|
parent.generate(1)
|
|
# Should succeed via wallet lookup for address match, and when given
|
|
raw_pegin = sidechain.createrawpegin(raw, proof)['hex']
|
|
signed_pegin = sidechain.signrawtransactionwithwallet(raw_pegin)
|
|
|
|
pegtxid1 = sidechain.claimpegin(raw, proof)
|
|
|
|
# Will invalidate the block that confirms this transaction later
|
|
self.sync_all(self.node_groups)
|
|
blockhash = sidechain2.generate(1)
|
|
self.sync_all(self.node_groups)
|
|
sidechain.generate(5)
|
|
|
|
tx1 = sidechain.gettransaction(pegtxid1)
|
|
|
|
print('tx1', tx1)
|
|
if "confirmations" in tx1 and tx1["confirmations"] == 6:
|
|
print("Peg-in is confirmed: Success!")
|
|
else:
|
|
raise Exception("Peg-in confirmation has failed.")
|
|
|
|
# Look at pegin fields
|
|
decoded = sidechain.decoderawtransaction(tx1["hex"])
|
|
assert decoded["vin"][0]["is_pegin"] == True
|
|
assert len(decoded["vin"][0]["pegin_witness"]) > 0
|
|
#TODO(rebase) CT assert fee here too
|
|
# Check that there's sufficient fee for the peg-in
|
|
#vsize = decoded["vsize"]
|
|
#fee_output = decoded["vout"][1]
|
|
#fallbackfee_pervbyte = Decimal("0.00001")/Decimal("1000")
|
|
#print("fee_output", fee_output)
|
|
#assert fee_output["scriptPubKey"]["type"] == "fee"
|
|
#assert fee_output["value"] >= fallbackfee_pervbyte*vsize
|
|
|
|
# Quick reorg checks of pegs
|
|
sidechain.invalidateblock(blockhash[0])
|
|
if sidechain.gettransaction(pegtxid1)["confirmations"] != 0:
|
|
raise Exception("Peg-in didn't unconfirm after invalidateblock call.")
|
|
# Re-enters block
|
|
sidechain.generate(1)
|
|
if sidechain.gettransaction(pegtxid1)["confirmations"] != 1:
|
|
raise Exception("Peg-in should have one confirm on side block.")
|
|
sidechain.reconsiderblock(blockhash[0])
|
|
if sidechain.gettransaction(pegtxid1)["confirmations"] != 6:
|
|
raise Exception("Peg-in should be back to 6 confirms.")
|
|
|
|
# Do multiple claims in mempool
|
|
n_claims = 6
|
|
|
|
print("Flooding mempool with many small claims")
|
|
pegtxs = []
|
|
sidechain.generate(101)
|
|
|
|
# Do mixture of raw peg-in and automatic peg-in tx construction
|
|
# where raw creation is done on another node
|
|
for i in range(n_claims):
|
|
addrs = sidechain.getpeginaddress()
|
|
txid = parent.sendtoaddress(addrs["mainchain_address"], 1)
|
|
parent.generate(1)
|
|
proof = parent.gettxoutproof([txid])
|
|
raw = parent.getrawtransaction(txid)
|
|
if i % 2 == 0:
|
|
parent.generate(11)
|
|
pegtxs += [sidechain.claimpegin(raw, proof)]
|
|
else:
|
|
# The raw API doesn't check for the additional 2 confirmation buffer
|
|
# So we only get 10 confirms then send off. Miners will add to block anyways.
|
|
|
|
# Don't mature whole way yet to test signing immature peg-in input
|
|
parent.generate(8)
|
|
# Wallet in sidechain2 gets funds instead of sidechain
|
|
raw_pegin = sidechain2.createrawpegin(raw, proof, addrs["claim_script"])["hex"]
|
|
# First node should also be able to make a valid transaction with or without 3rd arg
|
|
# since this wallet originated the claim_script itself
|
|
sidechain.createrawpegin(raw, proof, addrs["claim_script"])
|
|
sidechain.createrawpegin(raw, proof)
|
|
signed_pegin = sidechain.signrawtransactionwithwallet(raw_pegin)
|
|
assert(signed_pegin["complete"])
|
|
assert("warning" in signed_pegin) # warning for immature peg-in
|
|
# fully mature them now
|
|
parent.generate(1)
|
|
pegtxs += [sidechain.sendrawtransaction(signed_pegin["hex"])]
|
|
|
|
self.sync_all(self.node_groups)
|
|
sidechain2.generate(1)
|
|
for i, pegtxid in enumerate(pegtxs):
|
|
if i % 2 == 0:
|
|
tx = sidechain.gettransaction(pegtxid)
|
|
else:
|
|
tx = sidechain2.gettransaction(pegtxid)
|
|
if "confirmations" not in tx or tx["confirmations"] == 0:
|
|
raise Exception("Peg-in confirmation has failed.")
|
|
|
|
print("Test pegout")
|
|
self.test_pegout(get_new_unconfidential_address(parent), sidechain)
|
|
|
|
print("Test pegout P2SH")
|
|
parent_chain_addr = get_new_unconfidential_address(parent)
|
|
parent_pubkey = parent.getaddressinfo(parent_chain_addr)["pubkey"]
|
|
parent_chain_p2sh_addr = parent.createmultisig(1, [parent_pubkey])["address"]
|
|
self.test_pegout(parent_chain_p2sh_addr, sidechain)
|
|
|
|
print("Test pegout Garbage")
|
|
parent_chain_addr = "garbage"
|
|
try:
|
|
self.test_pegout(parent_chain_addr, sidechain)
|
|
raise Exception("A garbage address should fail.")
|
|
except JSONRPCException as e:
|
|
assert("Invalid Bitcoin address" in e.error["message"])
|
|
|
|
print("Test pegout Garbage valid")
|
|
prev_txid = sidechain.sendtoaddress(sidechain.getnewaddress(), 1)
|
|
sidechain.generate(1)
|
|
pegout_chain = 'a' * 64
|
|
pegout_hex = 'b' * 500
|
|
inputs = [{"txid": prev_txid, "vout": 0}]
|
|
outputs = {"vdata": [pegout_chain, pegout_hex]}
|
|
rawtx = sidechain.createrawtransaction(inputs, outputs)
|
|
raw_pegout = sidechain.decoderawtransaction(rawtx)
|
|
|
|
assert 'vout' in raw_pegout and len(raw_pegout['vout']) > 0
|
|
pegout_tested = False
|
|
for output in raw_pegout['vout']:
|
|
scriptPubKey = output['scriptPubKey']
|
|
if 'type' in scriptPubKey and scriptPubKey['type'] == 'nulldata':
|
|
assert ('pegout_hex' in scriptPubKey and 'pegout_asm' in scriptPubKey and 'pegout_type' in scriptPubKey)
|
|
assert ('pegout_chain' in scriptPubKey and 'pegout_reqSigs' not in scriptPubKey and 'pegout_addresses' not in scriptPubKey)
|
|
assert scriptPubKey['pegout_type'] == 'nonstandard'
|
|
assert scriptPubKey['pegout_chain'] == pegout_chain
|
|
assert scriptPubKey['pegout_hex'] == pegout_hex
|
|
pegout_tested = True
|
|
break
|
|
assert pegout_tested
|
|
|
|
print ("Now test failure to validate peg-ins based on intermittant bitcoind rpc failure")
|
|
self.stop_node(1)
|
|
txid = parent.sendtoaddress(addr, 1)
|
|
parent.generate(12)
|
|
proof = parent.gettxoutproof([txid])
|
|
raw = parent.getrawtransaction(txid)
|
|
sidechain.claimpegin(raw, proof) # stuck peg
|
|
sidechain.generate(1)
|
|
print("Waiting to ensure block is being rejected by sidechain2")
|
|
time.sleep(5)
|
|
|
|
assert(sidechain.getblockcount() != sidechain2.getblockcount())
|
|
|
|
print("Restarting parent2")
|
|
self.start_node(1)
|
|
#parent2 = self.nodes[1]
|
|
connect_nodes_bi(self.nodes, 0, 1)
|
|
|
|
# Don't make a block, race condition when pegin-invalid block
|
|
# is awaiting further validation, nodes reject subsequent blocks
|
|
# even ones they create
|
|
print("Now waiting for node to re-evaluate peg-in witness failed block... should take a few seconds")
|
|
self.sync_all(self.node_groups)
|
|
print("Completed!\n")
|
|
print("Now send funds out in two stages, partial, and full")
|
|
some_btc_addr = get_new_unconfidential_address(parent)
|
|
#TODO(rebase) CA bitcoin balances
|
|
#bal_1 = sidechain.getwalletinfo()["balance"]["bitcoin"]
|
|
bal_1 = sidechain.getwalletinfo()["balance"]
|
|
try:
|
|
sidechain.sendtomainchain(some_btc_addr, bal_1 + 1)
|
|
raise Exception("Sending out too much; should have failed")
|
|
except JSONRPCException as e:
|
|
assert("Insufficient funds" in e.error["message"])
|
|
|
|
#assert(sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1)
|
|
assert(sidechain.getwalletinfo()["balance"] == bal_1)
|
|
try:
|
|
sidechain.sendtomainchain(some_btc_addr+"b", bal_1 - 1)
|
|
raise Exception("Sending to invalid address; should have failed")
|
|
except JSONRPCException as e:
|
|
assert("Invalid Bitcoin address" in e.error["message"])
|
|
|
|
#assert(sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1)
|
|
assert(sidechain.getwalletinfo()["balance"] == bal_1)
|
|
try:
|
|
sidechain.sendtomainchain("1Nro9WkpaKm9axmcfPVp79dAJU1Gx7VmMZ", bal_1 - 1)
|
|
raise Exception("Sending to mainchain address when should have been testnet; should have failed")
|
|
except JSONRPCException as e:
|
|
assert("Invalid Bitcoin address" in e.error["message"])
|
|
|
|
#assert(sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1)
|
|
assert(sidechain.getwalletinfo()["balance"] == bal_1)
|
|
|
|
peg_out_txid = sidechain.sendtomainchain(some_btc_addr, 1)
|
|
|
|
peg_out_details = sidechain.decoderawtransaction(sidechain.getrawtransaction(peg_out_txid))
|
|
# peg-out, change
|
|
#TODO(rebase) CA/CT fee output
|
|
#assert(len(peg_out_details["vout"]) == 3)
|
|
assert(len(peg_out_details["vout"]) == 2)
|
|
found_pegout_value = False
|
|
for output in peg_out_details["vout"]:
|
|
if "value" in output and output["value"] == 1:
|
|
found_pegout_value = True
|
|
assert(found_pegout_value)
|
|
|
|
#bal_2 = sidechain.getwalletinfo()["balance"]["bitcoin"]
|
|
bal_2 = sidechain.getwalletinfo()["balance"]
|
|
# Make sure balance went down
|
|
assert(bal_2 + 1 < bal_1)
|
|
|
|
sidechain.sendtomainchain(some_btc_addr, bal_2, True)
|
|
|
|
#TODO(rebase) CA bitcoin balance
|
|
#assert("bitcoin" not in sidechain.getwalletinfo()["balance"])
|
|
assert(sidechain.getwalletinfo()["balance"] == 0)
|
|
|
|
print('Success!')
|
|
|
|
# Manually stop sidechains first, then the parent chains.
|
|
self.stop_node(2)
|
|
self.stop_node(3)
|
|
self.stop_node(0)
|
|
self.stop_node(1)
|
|
|
|
if __name__ == '__main__':
|
|
FedPegTest().main()
|