From 870e41609e206d453601629e06ce4e4e4ed83dc4 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Mon, 25 Mar 2019 12:11:03 -0400 Subject: [PATCH 1/2] Enforce coinbase maturity on bitcoin peg-ins --- src/pegins.cpp | 16 +++++++++---- src/wallet/rpcwallet.cpp | 6 ++++- test/functional/feature_fedpeg.py | 38 +++++++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/pegins.cpp b/src/pegins.cpp index 19ef359e1f..74349edbc8 100644 --- a/src/pegins.cpp +++ b/src/pegins.cpp @@ -189,7 +189,7 @@ static bool CheckPeginTx(const std::vector& tx_data, T& pegtx, co } template -static bool GetBlockAndTxFromMerkleBlock(uint256& block_hash, uint256& tx_hash, T& merkle_block, const std::vector& merkle_block_raw) +static bool GetBlockAndTxFromMerkleBlock(uint256& block_hash, uint256& tx_hash, unsigned int& tx_index, T& merkle_block, const std::vector& merkle_block_raw) { try { std::vector tx_hashes; @@ -205,6 +205,7 @@ static bool GetBlockAndTxFromMerkleBlock(uint256& block_hash, uint256& tx_hash, return false; } tx_hash = tx_hashes[0]; + tx_index = tx_indices[0]; } catch (std::exception& e) { // Invalid encoding of merkle block return false; @@ -295,10 +296,11 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p uint256 block_hash; uint256 tx_hash; int num_txs; + unsigned int tx_index = 0; // Get txout proof if (Params().GetConsensus().ParentChainHasPow()) { Sidechain::Bitcoin::CMerkleBlock merkle_block_pow; - if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, merkle_block_pow, stack[5])) { + if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, tx_index, merkle_block_pow, stack[5])) { err_msg = "Could not extract block and tx from merkleblock."; return false; } @@ -316,7 +318,7 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p num_txs = merkle_block_pow.txn.GetNumTransactions(); } else { CMerkleBlock merkle_block; - if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, merkle_block, stack[5])) { + if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, tx_index, merkle_block, stack[5])) { err_msg = "Could not extract block and tx from merkleblock."; return false; } @@ -354,7 +356,13 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p // Finally, validate peg-in via rpc call if (check_depth && gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) { - if (!IsConfirmedBitcoinBlock(block_hash, Params().GetConsensus().pegin_min_depth, num_txs)) { + unsigned int required_depth = Params().GetConsensus().pegin_min_depth; + // Don't allow coinbase output claims before coinbase maturity + if (tx_index == 0) { + required_depth = std::max(required_depth, (unsigned int)COINBASE_MATURITY); + } + LogPrintf("Required depth: %d\n", required_depth); + if (!IsConfirmedBitcoinBlock(block_hash, required_depth, num_txs)) { err_msg = "Needs more confirmations."; return false; } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 3e2572c91c..0266c1f97a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5240,7 +5240,11 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef // Additional block lee-way to avoid bitcoin block races if (gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) { - ret.pushKV("mature", IsConfirmedBitcoinBlock(merkleBlock.header.GetHash(), Params().GetConsensus().pegin_min_depth+2, merkleBlock.txn.GetNumTransactions())); + unsigned int required_depth = Params().GetConsensus().pegin_min_depth + 2; + if (txIndices[0] == 0) { + required_depth = std::max(required_depth, (unsigned int)COINBASE_MATURITY+2); + } + ret.pushKV("mature", IsConfirmedBitcoinBlock(merkleBlock.header.GetHash(), required_depth, merkleBlock.txn.GetNumTransactions())); } return ret; diff --git a/test/functional/feature_fedpeg.py b/test/functional/feature_fedpeg.py index e8ea452c3a..f130767099 100755 --- a/test/functional/feature_fedpeg.py +++ b/test/functional/feature_fedpeg.py @@ -10,6 +10,8 @@ get_datadir_path, rpc_port, p2p_port, + assert_raises_rpc_error, + assert_equal, ) from decimal import Decimal @@ -53,9 +55,10 @@ def setup_network(self, split=False): else: extra_args.extend([ "-validatepegin=0", - "-initialfreecoins=2100000000000000", + "-initialfreecoins=0", "-anyonecanspendaremine", "-signblockscript=51", # OP_TRUE + '-con_blocksubsidy=5000000000', ]) self.add_nodes(1, [extra_args], chain=[parent_chain], binary=parent_binary) @@ -390,7 +393,38 @@ def run_test(self): # Make sure balance went down assert(bal_2 + 1 < bal_1) - sidechain.sendtomainchain(some_btc_addr, bal_2, True) + # Have bitcoin output go directly into a claim output + pegin_info = sidechain.getpeginaddress() + mainchain_addr = pegin_info["mainchain_address"] + claim_script = pegin_info["claim_script"] + # Watch the address so we can get tx without txindex + parent.importaddress(mainchain_addr) + claim_block = parent.generatetoaddress(50, mainchain_addr)[0] + 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()) + # 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()) + assert_equal(sidechain.gettransaction(claim_txid)["confirmations"], 1) + + + # Send rest of coins using subtractfee from output arg + sidechain.sendtomainchain(some_btc_addr, sidechain.getwalletinfo()["balance"]['bitcoin'], True) assert(sidechain.getwalletinfo()["balance"]['bitcoin'] == 0) From 5acc42e4ebe2a3038195045ba7d5084650eb5f62 Mon Sep 17 00:00:00 2001 From: Gregory Sanders Date: Thu, 28 Mar 2019 16:32:34 -0400 Subject: [PATCH 2/2] feature_fedpeg.py: remove the spam output --- test/functional/feature_fedpeg.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/test/functional/feature_fedpeg.py b/test/functional/feature_fedpeg.py index f130767099..0e9180c475 100755 --- a/test/functional/feature_fedpeg.py +++ b/test/functional/feature_fedpeg.py @@ -67,7 +67,6 @@ def setup_network(self, split=False): connect_nodes_bi(self.nodes, 0, 1) self.parentgenesisblockhash = self.nodes[0].getblockhash(0) - print('parentgenesisblockhash', self.parentgenesisblockhash) if not self.options.parent_bitcoin: parent_pegged_asset = self.nodes[0].getsidechaininfo()['pegged_asset'] @@ -150,8 +149,6 @@ def run_test(self): 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) @@ -159,15 +156,13 @@ def run_test(self): proof = parent.gettxoutproof([txid1]) raw = parent.getrawtransaction(txid1) - print('raw', parent.getrawtransaction(txid1, True)) - print("Attempting peg-in") + 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: - 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 @@ -176,14 +171,12 @@ def run_test(self): 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 @@ -192,7 +185,6 @@ def run_test(self): 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 @@ -211,7 +203,6 @@ def run_test(self): tx1 = sidechain.gettransaction(pegtxid1) - print('tx1', tx1) if "confirmations" in tx1 and tx1["confirmations"] == 6: print("Peg-in is confirmed: Success!") else: @@ -225,7 +216,6 @@ def run_test(self): 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 @@ -244,7 +234,7 @@ def run_test(self): # Do multiple claims in mempool n_claims = 6 - print("Flooding mempool with many small claims") + print("Flooding mempool with a few claims") pegtxs = [] sidechain.generate(101) @@ -344,7 +334,6 @@ def run_test(self): 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 @@ -393,10 +382,16 @@ def run_test(self): # 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"] - claim_script = pegin_info["claim_script"] # Watch the address so we can get tx without txindex parent.importaddress(mainchain_addr) claim_block = parent.generatetoaddress(50, mainchain_addr)[0] @@ -422,12 +417,6 @@ def run_test(self): sidechain.generatetoaddress(1, sidechain.getnewaddress()) assert_equal(sidechain.gettransaction(claim_txid)["confirmations"], 1) - - # Send rest of coins using subtractfee from output arg - sidechain.sendtomainchain(some_btc_addr, sidechain.getwalletinfo()["balance"]['bitcoin'], True) - - assert(sidechain.getwalletinfo()["balance"]['bitcoin'] == 0) - print('Success!') # Manually stop sidechains first, then the parent chains.