mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
Fix multiple issues with claimpegin and createrawpegin support for multiple loaded wallets: - Failure to call EnsureWalletIsAvailable would cause a crash when multiple wallets were loaded, but one was not specified in the RPC call. - Failure to propagate the request URL from claimpegin when making an internal call to another RPC would result in failure when multiple wallets were loaded, by failing to specify one for that call. Add a regression test to the feature_fedpeg.py test: at a critical point, create a second wallet on the sidechaind, and set up the RPC client to use the first one, to check that it still works correctly.
649 lines
32 KiB
Python
Executable file
649 lines
32 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,
|
|
assert_raises_rpc_error,
|
|
assert_equal,
|
|
bytes_to_hex_str,
|
|
hex_str_to_bytes,
|
|
)
|
|
from test_framework import util
|
|
from test_framework.messages import (
|
|
COIN,
|
|
CBlock,
|
|
COutPoint,
|
|
CTransaction,
|
|
CTxIn,
|
|
CTxInWitness,
|
|
CTxOut,
|
|
CTxOutNonce,
|
|
FromHex,
|
|
ToHex,
|
|
)
|
|
from test_framework.blocktools import (
|
|
add_witness_commitment,
|
|
)
|
|
from decimal import Decimal
|
|
|
|
def get_new_unconfidential_address(node, addr_type="p2sh-segwit"):
|
|
addr = node.getnewaddress("", addr_type)
|
|
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")
|
|
parser.add_argument("--pre_transition", dest="pre_transition", default=False, action="store_true",
|
|
help="Run test in dynafed activated chain, without a transition")
|
|
parser.add_argument("--post_transition", dest="post_transition", default=False, action="store_true",
|
|
help="Run test in dynafed activated chain, after transition and additional epoch to invalidate old fedpegscript")
|
|
|
|
def skip_test_if_missing_module(self):
|
|
self.skip_if_no_wallet()
|
|
|
|
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 = "elementsregtest" if not self.options.parent_bitcoin else "regtest"
|
|
parent_binary = [self.options.parent_binpath] if self.options.parent_binpath != "" else None
|
|
for n in range(2):
|
|
extra_args = [
|
|
"-port="+str(p2p_port(n)),
|
|
"-rpcport="+str(rpc_port(n))
|
|
]
|
|
if self.options.parent_bitcoin:
|
|
# bitcoind can't read elements.conf config files
|
|
extra_args.extend([
|
|
"-regtest=1",
|
|
"-printtoconsole=0",
|
|
"-server=1",
|
|
"-discover=0",
|
|
"-keypool=1",
|
|
"-listenonion=0",
|
|
"-addresstype=legacy", # To make sure bitcoind gives back p2pkh no matter version
|
|
])
|
|
else:
|
|
extra_args.extend([
|
|
"-validatepegin=0",
|
|
"-initialfreecoins=0",
|
|
"-anyonecanspendaremine=1",
|
|
"-signblockscript=51", # OP_TRUE
|
|
])
|
|
|
|
self.add_nodes(1, [extra_args], chain=[parent_chain], binary=parent_binary, chain_in_args=[not self.options.parent_bitcoin])
|
|
self.start_node(n)
|
|
print("Node {} started".format(n))
|
|
# set hard-coded mining keys for non-Elements chains
|
|
if self.options.parent_bitcoin:
|
|
self.nodes[0].set_deterministic_priv_key('2Mysp7FKKe52eoC2JmU46irt1dt58TpCvhQ', 'cTNbtVJmhx75RXomhYWSZAafuNNNKPd1cr2ZiUcAeukLNGrHWjvJ')
|
|
self.nodes[1].set_deterministic_priv_key('2N19ZHF3nEzBXzkaZ3N5sVBJXQ8jZ7Udpg5', 'cRnDSw1JsjmYYEN6xxQvf5pqMENsRE584z6MdWfJ7v85c4ciitkk')
|
|
|
|
connect_nodes_bi(self.nodes, 0, 1)
|
|
self.parentgenesisblockhash = self.nodes[0].getblockhash(0)
|
|
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)),
|
|
'-validatepegin=1',
|
|
'-fedpegscript=%s' % self.fedpeg_script,
|
|
'-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
|
|
'-parentgenesisblockhash=%s' % self.parentgenesisblockhash,
|
|
'-parentpubkeyprefix=111',
|
|
'-parentscriptprefix=196',
|
|
'-parent_bech32_hrp=bcrt',
|
|
# Turn of consistency checks that can cause assert when parent node stops
|
|
# and a peg-in transaction fails this belt-and-suspenders check.
|
|
'-checkmempool=0',
|
|
]
|
|
if not self.options.parent_bitcoin:
|
|
extra_args.extend([
|
|
'-parentpubkeyprefix=235',
|
|
'-parentscriptprefix=75',
|
|
'-parent_bech32_hrp=ert',
|
|
'-con_parent_chain_signblockscript=51',
|
|
'-con_parent_pegged_asset=%s' % parent_pegged_asset,
|
|
])
|
|
|
|
# Immediate activation of dynafed when requested versus "never" from conf
|
|
if self.options.pre_transition or self.options.post_transition:
|
|
extra_args.extend(["-con_dyna_deploy_start=-1"])
|
|
|
|
# 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=["elementsregtest"])
|
|
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
|
|
sidechain.generatetoaddress(1, sidechain.getnewaddress())
|
|
assert_equal(sidechain.gettransaction(pegout_txid)["confirmations"], 1)
|
|
|
|
def run_test(self):
|
|
parent = self.nodes[0]
|
|
#parent2 = self.nodes[1]
|
|
sidechain = self.nodes[2]
|
|
sidechain2 = self.nodes[3]
|
|
|
|
# If we're testing post-transition, force a fedpegscript transition and
|
|
# getting rid of old fedpegscript by making at least another epoch pass by
|
|
WSH_OP_TRUE = self.nodes[0].decodescript("51")["segwit"]["hex"]
|
|
# We just randomize the keys a bit to get another valid fedpegscript
|
|
new_fedpegscript = sidechain.tweakfedpegscript("f00dbabe")["script"]
|
|
if self.options.post_transition:
|
|
print("Running test post-transition")
|
|
for _ in range(30):
|
|
block_hex = sidechain.getnewblockhex(0, {"signblockscript":WSH_OP_TRUE, "max_block_witness":10, "fedpegscript":new_fedpegscript, "extension_space":[]})
|
|
sidechain.submitblock(block_hex)
|
|
assert_equal(sidechain.getsidechaininfo()["current_fedpegscripts"], [new_fedpegscript]*2)
|
|
|
|
if self.options.pre_transition:
|
|
print("Running test pre-transition, dynafed activated from first block")
|
|
|
|
for node in self.nodes:
|
|
node.importprivkey(privkey=node.get_deterministic_priv_key().key, label="mining")
|
|
util.node_fastmerkle = sidechain
|
|
|
|
parent.generate(101)
|
|
sidechain.generate(101)
|
|
self.log.info("sidechain info: {}".format(sidechain.getsidechaininfo()))
|
|
|
|
addrs = sidechain.getpeginaddress()
|
|
addr = addrs["mainchain_address"]
|
|
assert_equal(sidechain.decodescript(addrs["claim_script"])["type"], "witness_v0_keyhash")
|
|
txid1 = parent.sendtoaddress(addr, 24)
|
|
# 10+2 confirms required to get into mempool and confirm
|
|
assert_equal(sidechain.getsidechaininfo()["pegin_confirmation_depth"], 10)
|
|
parent.generate(1)
|
|
time.sleep(2)
|
|
proof = parent.gettxoutproof([txid1])
|
|
|
|
raw = parent.gettransaction(txid1)["hex"]
|
|
|
|
# Create a wallet in order to test that multi-wallet support works correctly for claimpegin
|
|
# (Regression test for https://github.com/ElementsProject/elements/issues/812 .)
|
|
sidechain.createwallet("throwaway")
|
|
# Set up our sidechain RPCs to use the first wallet (with empty name). We do this by
|
|
# overriding the RPC object in a hacky way, to avoid breaking a different hack on TestNode
|
|
# that enables generate() to work despite the deprecation of the generate RPC.
|
|
sidechain.rpc = sidechain.get_wallet_rpc("")
|
|
|
|
print("Attempting peg-ins")
|
|
# 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:
|
|
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:
|
|
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:
|
|
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:
|
|
assert("Given claim_script does not match the given Bitcoin transaction." in e.error["message"])
|
|
|
|
# 12 confirms allows in mempool
|
|
parent.generate(1)
|
|
|
|
# Make sure that a tx with a duplicate pegin claim input gets rejected.
|
|
raw_pegin = sidechain.createrawpegin(raw, proof)["hex"]
|
|
raw_pegin = FromHex(CTransaction(), raw_pegin)
|
|
raw_pegin.vin.append(raw_pegin.vin[0]) # duplicate the pegin input
|
|
raw_pegin = sidechain.signrawtransactionwithwallet(raw_pegin.serialize().hex())["hex"]
|
|
assert_raises_rpc_error(-26, "bad-txns-inputs-duplicate", sidechain.sendrawtransaction, raw_pegin)
|
|
# Also try including this tx in a block manually and submitting it.
|
|
doublespendblock = FromHex(CBlock(), sidechain.getnewblockhex())
|
|
doublespendblock.vtx.append(FromHex(CTransaction(), raw_pegin))
|
|
doublespendblock.hashMerkleRoot = doublespendblock.calc_merkle_root()
|
|
add_witness_commitment(doublespendblock)
|
|
doublespendblock.solve()
|
|
block_hex = bytes_to_hex_str(doublespendblock.serialize(True))
|
|
assert_raises_rpc_error(-25, "bad-txns-inputs-duplicate", sidechain.testproposedblock, block_hex, True)
|
|
|
|
# Should succeed via wallet lookup for address match, and when given
|
|
raw_pegin = sidechain.createrawpegin(raw, proof)['hex']
|
|
signed_pegin = sidechain.signrawtransactionwithwallet(raw_pegin)
|
|
|
|
sample_pegin_struct = FromHex(CTransaction(), signed_pegin["hex"])
|
|
# Round-trip peg-in transaction using python serialization
|
|
assert_equal(signed_pegin["hex"], sample_pegin_struct.serialize().hex())
|
|
# Store this for later (evil laugh)
|
|
sample_pegin_witness = sample_pegin_struct.wit.vtxinwit[0].peginWitness
|
|
|
|
pegtxid1 = sidechain.claimpegin(raw, proof)
|
|
# Make sure a second pegin claim does not get accepted in the mempool when
|
|
# another mempool tx already claims that pegin.
|
|
assert_raises_rpc_error(-4, "txn-mempool-conflict", 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)
|
|
|
|
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
|
|
# 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")
|
|
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-org causes peg-ins to get booted(wallet will resubmit in 10 minutes)
|
|
assert_equal(sidechain.getrawmempool(), [])
|
|
sidechain.sendrawtransaction(tx1["hex"])
|
|
|
|
# Create duplicate claim, put it in block along with current one in mempool
|
|
# to test duplicate-in-block claims between two txs that are in the same block.
|
|
raw_pegin = sidechain.createrawpegin(raw, proof)["hex"]
|
|
raw_pegin = sidechain.signrawtransactionwithwallet(raw_pegin)["hex"]
|
|
raw_pegin = FromHex(CTransaction(), raw_pegin)
|
|
doublespendblock = FromHex(CBlock(), sidechain.getnewblockhex())
|
|
assert(len(doublespendblock.vtx) == 2) # coinbase and pegin
|
|
doublespendblock.vtx.append(raw_pegin)
|
|
doublespendblock.hashMerkleRoot = doublespendblock.calc_merkle_root()
|
|
add_witness_commitment(doublespendblock)
|
|
doublespendblock.solve()
|
|
block_hex = bytes_to_hex_str(doublespendblock.serialize(True))
|
|
assert_raises_rpc_error(-25, "bad-txns-double-pegin", sidechain.testproposedblock, block_hex, True)
|
|
|
|
# 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.")
|
|
|
|
# Now the pegin is already claimed in a confirmed tx.
|
|
# In that case, a duplicate claim should (1) not be accepted in the mempool
|
|
# and (2) not be accepted in a block.
|
|
assert_raises_rpc_error(-4, "pegin-already-claimed", sidechain.claimpegin, raw, proof)
|
|
# For case (2), manually craft a block and include the tx.
|
|
doublespendblock = FromHex(CBlock(), sidechain.getnewblockhex())
|
|
doublespendblock.vtx.append(raw_pegin)
|
|
doublespendblock.hashMerkleRoot = doublespendblock.calc_merkle_root()
|
|
add_witness_commitment(doublespendblock)
|
|
doublespendblock.solve()
|
|
block_hex = bytes_to_hex_str(doublespendblock.serialize(True))
|
|
assert_raises_rpc_error(-25, "bad-txns-double-pegin", sidechain.testproposedblock, block_hex, True)
|
|
|
|
# Do multiple claims in mempool
|
|
n_claims = 6
|
|
|
|
print("Flooding mempool with a few 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.gettransaction(txid)["hex"]
|
|
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 pegouts")
|
|
self.test_pegout(get_new_unconfidential_address(parent, "legacy"), sidechain)
|
|
self.test_pegout(get_new_unconfidential_address(parent, "p2sh-segwit"), sidechain)
|
|
self.test_pegout(get_new_unconfidential_address(parent, "bech32"), 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 intermittent bitcoind rpc failure")
|
|
self.stop_node(1)
|
|
txid = parent.sendtoaddress(addr, 1)
|
|
parent.generate(12)
|
|
proof = parent.gettxoutproof([txid])
|
|
raw = parent.gettransaction(txid)["hex"]
|
|
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)
|
|
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)
|
|
bal_1 = sidechain.getwalletinfo()["balance"]['bitcoin']
|
|
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)
|
|
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)
|
|
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)
|
|
|
|
# Test superfluous peg-in witness data on regular spend before we have no funds
|
|
raw_spend = sidechain.createrawtransaction([], {sidechain.getnewaddress():1})
|
|
fund_spend = sidechain.fundrawtransaction(raw_spend)
|
|
sign_spend = sidechain.signrawtransactionwithwallet(fund_spend["hex"])
|
|
signed_struct = FromHex(CTransaction(), sign_spend["hex"])
|
|
# Non-witness tx has no witness serialized yet
|
|
if len(signed_struct.wit.vtxinwit) == 0:
|
|
signed_struct.wit.vtxinwit = [CTxInWitness()]
|
|
signed_struct.wit.vtxinwit[0].peginWitness.stack = sample_pegin_witness.stack
|
|
assert_equal(sidechain.testmempoolaccept([signed_struct.serialize().hex()])[0]["allowed"], False)
|
|
assert_equal(sidechain.testmempoolaccept([signed_struct.serialize().hex()])[0]["reject-reason"], "68: extra-pegin-witness")
|
|
signed_struct.wit.vtxinwit[0].peginWitness.stack = [b'\x00'*100000] # lol
|
|
assert_equal(sidechain.testmempoolaccept([signed_struct.serialize().hex()])[0]["allowed"], False)
|
|
assert_equal(sidechain.testmempoolaccept([signed_struct.serialize().hex()])[0]["reject-reason"], "68: extra-pegin-witness")
|
|
|
|
peg_out_txid = sidechain.sendtomainchain(some_btc_addr, 1)
|
|
|
|
peg_out_details = sidechain.decoderawtransaction(sidechain.getrawtransaction(peg_out_txid))
|
|
# peg-out, change, fee
|
|
assert(len(peg_out_details["vout"]) == 3)
|
|
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"]
|
|
# Make sure balance went down
|
|
assert(bal_2 + 1 < bal_1)
|
|
|
|
# Send rest of coins using subtractfee from output arg
|
|
sidechain.sendtomainchain(some_btc_addr, bal_2, True)
|
|
|
|
assert(sidechain.getwalletinfo()["balance"]['bitcoin'] == 0)
|
|
|
|
print('Test coinbase peg-in maturity rules')
|
|
|
|
# Have bitcoin output go directly into a claim output
|
|
pegin_info = sidechain.getpeginaddress()
|
|
mainchain_addr = pegin_info["mainchain_address"]
|
|
# Watch the address so we can get tx without txindex
|
|
parent.importaddress(mainchain_addr)
|
|
claim_block = parent.generatetoaddress(50, mainchain_addr)[0]
|
|
self.sync_all(self.node_groups)
|
|
block_coinbase = parent.getblock(claim_block, 2)["tx"][0]
|
|
claim_txid = block_coinbase["txid"]
|
|
claim_tx = block_coinbase["hex"]
|
|
claim_proof = parent.gettxoutproof([claim_txid], claim_block)
|
|
|
|
# Can't claim something even though it has 50 confirms since it's coinbase
|
|
assert_raises_rpc_error(-8, "Peg-in Bitcoin transaction needs more confirmations to be sent.", sidechain.claimpegin, claim_tx, claim_proof)
|
|
# If done via raw API, still doesn't work
|
|
coinbase_pegin = sidechain.createrawpegin(claim_tx, claim_proof)
|
|
assert_equal(coinbase_pegin["mature"], False)
|
|
signed_pegin = sidechain.signrawtransactionwithwallet(coinbase_pegin["hex"])["hex"]
|
|
assert_raises_rpc_error(-26, "bad-pegin-witness, Needs more confirmations.", sidechain.sendrawtransaction, signed_pegin)
|
|
|
|
# 50 more blocks to allow wallet to make it succeed by relay and consensus
|
|
parent.generatetoaddress(50, parent.getnewaddress())
|
|
self.sync_all(self.node_groups)
|
|
# Wallet still doesn't want to for 2 more confirms
|
|
assert_equal(sidechain.createrawpegin(claim_tx, claim_proof)["mature"], False)
|
|
# But we can just shoot it off
|
|
claim_txid = sidechain.sendrawtransaction(signed_pegin)
|
|
sidechain.generatetoaddress(1, sidechain.getnewaddress())
|
|
self.sync_all(self.node_groups)
|
|
assert_equal(sidechain.gettransaction(claim_txid)["confirmations"], 1)
|
|
|
|
# Test a confidential pegin.
|
|
print("Performing a confidential pegin.")
|
|
# start pegin
|
|
pegin_addrs = sidechain.getpeginaddress()
|
|
assert_equal(sidechain.decodescript(pegin_addrs["claim_script"])["type"], "witness_v0_keyhash")
|
|
pegin_addr = addrs["mainchain_address"]
|
|
txid_fund = parent.sendtoaddress(pegin_addr, 10)
|
|
# 10+2 confirms required to get into mempool and confirm
|
|
parent.generate(11)
|
|
self.sync_all(self.node_groups)
|
|
proof = parent.gettxoutproof([txid_fund])
|
|
raw = parent.gettransaction(txid_fund)["hex"]
|
|
raw_pegin = sidechain.createrawpegin(raw, proof)['hex']
|
|
pegin = FromHex(CTransaction(), raw_pegin)
|
|
# add new blinding pubkey for the pegin output
|
|
pegin.vout[0].nNonce = CTxOutNonce(hex_str_to_bytes(sidechain.getaddressinfo(sidechain.getnewaddress("", "blech32"))["confidential_key"]))
|
|
# now add an extra input and output from listunspent; we need a blinded output for this
|
|
blind_addr = sidechain.getnewaddress("", "blech32")
|
|
sidechain.sendtoaddress(blind_addr, 15)
|
|
sidechain.generate(6)
|
|
# Make sure sidechain2 knows about the same input
|
|
self.sync_all(self.node_groups)
|
|
unspent = [u for u in sidechain.listunspent(6, 6) if u["amount"] == 15][0]
|
|
assert(unspent["spendable"])
|
|
assert("amountcommitment" in unspent)
|
|
pegin.vin.append(CTxIn(COutPoint(int(unspent["txid"], 16), unspent["vout"])))
|
|
# insert corresponding output before fee output
|
|
new_destination = sidechain.getaddressinfo(sidechain.getnewaddress("", "blech32"))
|
|
new_dest_script_pk = hex_str_to_bytes(new_destination["scriptPubKey"])
|
|
new_dest_nonce = CTxOutNonce(hex_str_to_bytes(new_destination["confidential_key"]))
|
|
new_dest_asset = pegin.vout[0].nAsset
|
|
pegin.vout.insert(1, CTxOut(int(unspent["amount"]*COIN) - 10000, new_dest_script_pk, new_dest_asset, new_dest_nonce))
|
|
# add the 10 ksat fee
|
|
pegin.vout[2].nValue.setToAmount(pegin.vout[2].nValue.getAmount() + 10000)
|
|
pegin_hex = ToHex(pegin)
|
|
# test with both blindraw and rawblindraw
|
|
raw_pegin_blinded1 = sidechain.blindrawtransaction(pegin_hex)
|
|
raw_pegin_blinded2 = sidechain.rawblindrawtransaction(pegin_hex, ["", unspent["amountblinder"]], [10, 15], [unspent["asset"]]*2, ["", unspent["assetblinder"]], "", False)
|
|
pegin_signed1 = sidechain.signrawtransactionwithwallet(raw_pegin_blinded1)
|
|
pegin_signed2 = sidechain.signrawtransactionwithwallet(raw_pegin_blinded2)
|
|
for pegin_signed in [pegin_signed1, pegin_signed2]:
|
|
final_decoded = sidechain.decoderawtransaction(pegin_signed["hex"])
|
|
assert(final_decoded["vin"][0]["is_pegin"])
|
|
assert(not final_decoded["vin"][1]["is_pegin"])
|
|
assert("assetcommitment" in final_decoded["vout"][0])
|
|
assert("valuecommitment" in final_decoded["vout"][0])
|
|
assert("commitmentnonce" in final_decoded["vout"][0])
|
|
assert("value" not in final_decoded["vout"][0])
|
|
assert("asset" not in final_decoded["vout"][0])
|
|
assert(final_decoded["vout"][0]["commitmentnonce_fully_valid"])
|
|
assert("assetcommitment" in final_decoded["vout"][1])
|
|
assert("valuecommitment" in final_decoded["vout"][1])
|
|
assert("commitmentnonce" in final_decoded["vout"][1])
|
|
assert("value" not in final_decoded["vout"][1])
|
|
assert("asset" not in final_decoded["vout"][1])
|
|
assert(final_decoded["vout"][1]["commitmentnonce_fully_valid"])
|
|
assert("value" in final_decoded["vout"][2])
|
|
assert("asset" in final_decoded["vout"][2])
|
|
# check that it is accepted in either mempool
|
|
accepted = sidechain.testmempoolaccept([pegin_signed["hex"]])[0]
|
|
if not accepted["allowed"]:
|
|
raise Exception(accepted["reject-reason"])
|
|
accepted = sidechain2.testmempoolaccept([pegin_signed["hex"]])[0]
|
|
if not accepted["allowed"]:
|
|
raise Exception(accepted["reject-reason"])
|
|
print("Blinded transaction looks ok!") # need this print to distinguish failures in for loop
|
|
|
|
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()
|