diff --git a/test/functional/feature_dynafed.py b/test/functional/feature_dynafed.py index df48956e77..e5af01421e 100755 --- a/test/functional/feature_dynafed.py +++ b/test/functional/feature_dynafed.py @@ -29,6 +29,9 @@ initial_extension = [initial_online+initial_online] new_extension = [initial_offline+initial_online] +ERR_MP_INVALID_PEGOUT = "64: invalid-pegout-proof" +ERR_MP_INVALID_PEGIN = "16: pegin-no-witness" + def go_to_epoch_end(node): epoch_info = node.getblockchaininfo() blocks_to_mine = epoch_info["epoch_length"] - epoch_info["epoch_age"] - 1 @@ -438,6 +441,120 @@ def test_transition_mempool_eject(self): assert_raises_rpc_error(-26, "invalid-pegout-proof", self.nodes[0].sendrawtransaction, raw_pegout) assert_raises_rpc_error(-26, "pegin-no-witness, Peg-in tx is invalid.", self.nodes[0].sendrawtransaction, raw_claim) + def assert_accepted(self, tx): + ret = self.nodes[0].testmempoolaccept([tx])[0] + assert ret["allowed"], ret["reject-reason"] + + def test_valid_epochs(self): + self.log.info("Testing pegins and pegouts stay valid for some epochs") + # previous test leaves us at age 9 + self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress()) + self.sync_all() + assert_equal(self.nodes[1].getblockchaininfo()["epoch_age"], 0) + + # signblockscript is OP_TRUE, let's transition to something we can PAK peg-out to + # and OP_TRUE fedpegscript, and set signblockscript back to OP_TRUE + + WSH_OP_TRUE = self.nodes[0].decodescript("51")["segwit"]["hex"] + xpub = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B" + init_details = self.nodes[1].initpegoutwallet(xpub) + pak_entry = init_details["pakentry"] + # stitch the extension space together using the relevant keys + extension_space = [pak_entry[4:4+66]+pak_entry[4+66+1:]] + pak_prop = {"signblockscript":WSH_OP_TRUE, "max_block_witness":3, "fedpegscript":"51", "extension_space":extension_space} + # generate blocks with the new proposal + for _ in range(9): + self.nodes[1].submitblock(self.nodes[1].getnewblockhex(0, pak_prop)) + + self.sync_all() + assert_equal(self.nodes[1].getblockchaininfo()["epoch_age"], 9) + assert_equal(self.nodes[1].getblockchaininfo()["current_signblock_hex"], WSH_OP_TRUE) + assert_equal(self.nodes[1].getsidechaininfo()["current_fedpegscripts"], ["51", "52"]) + + # Transactions + + # pegout prep is easy, just pegout + pegout_tx = self.nodes[1].gettransaction(self.nodes[1].sendtomainchain("", 1)["txid"])["hex"] + self.assert_accepted(pegout_tx) + + # Peg-in prep: + # hack: since we're not validating peg-ins in parent chain, just make + # both the funding and claim tx on same chain (printing money) + fund_info = self.nodes[0].getpeginaddress() + peg_id = self.nodes[0].sendtoaddress(fund_info["mainchain_address"], 1) + peg_tx = self.nodes[0].gettransaction(peg_id)["hex"] + # we need the confirmation of the peg tx, so we can't easily assert + # that the pegin tx would be accepted at this very point + self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress()) + proof = self.nodes[0].gettxoutproof([peg_id]) + pegin_tx = self.nodes[0].createrawpegin(peg_tx, proof, fund_info["claim_script"])["hex"] + pegin_tx = self.nodes[0].signrawtransactionwithwallet(pegin_tx)["hex"] + # both should be allowed after that block + assert_equal(self.nodes[0].getsidechaininfo()["current_fedpegscripts"], ["51", "52"]) + self.assert_accepted(pegin_tx) + self.assert_accepted(pegout_tx) + + # let's generate 20 blocks to pass through 2 new epochs without there being a transition + for _ in range(20): + self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress()) + self.assert_accepted(pegin_tx) + self.assert_accepted(pegout_tx) + assert_equal(self.nodes[0].getsidechaininfo()["current_fedpegscripts"], ["51", "51"]) + + # Now have node 1 transition to new pak and fedpegscript + pak_prop["fedpegscript"] = "52" + pak_prop["extension_space"] = initial_extension + for _ in range(9): + self.assert_accepted(pegin_tx) + self.assert_accepted(pegout_tx) + self.nodes[1].submitblock(self.nodes[1].getnewblockhex(0, pak_prop)) + sync_blocks(self.nodes) + + # so right before the next epoch, the new params are active and + # the pegout is already invalid while the pegin is still valid + self.assert_accepted(pegin_tx) + assert_equal(self.nodes[0].testmempoolaccept([pegout_tx])[0]["reject-reason"], ERR_MP_INVALID_PEGOUT) + # lets go back one block and make sure we can mine the pegout tx + old_tip = self.nodes[0].getbestblockhash() + self.nodes[0].invalidateblock(old_tip) + pegout_txid = self.nodes[0].sendrawtransaction(pegout_tx) + self.nodes[0].submitblock(self.nodes[0].getnewblockhex(0, pak_prop)) + tip = self.nodes[0].getbestblockhash() + assert_equal(self.nodes[0].getrawtransaction(pegout_txid, True, tip)["confirmations"], 1) + # undo it again so that we can make sure it is no longer allowed after this point + self.nodes[0].invalidateblock(tip) + self.nodes[0].reconsiderblock(old_tip) + + # and after the 10th block of course that is still the case + self.nodes[1].submitblock(self.nodes[1].getnewblockhex(0, pak_prop)) + sync_blocks(self.nodes) + self.assert_accepted(pegin_tx) + assert_equal(self.nodes[0].testmempoolaccept([pegout_tx])[0]["reject-reason"], ERR_MP_INVALID_PEGOUT) + + # We're in the next epoch, in this one the pegin tx should be accepted up until the last block + assert_equal(self.nodes[1].getsidechaininfo()["current_fedpegscripts"], ["52", "51"]) + for _ in range(9): + self.assert_accepted(pegin_tx) + assert_equal(self.nodes[0].testmempoolaccept([pegout_tx])[0]["reject-reason"], ERR_MP_INVALID_PEGOUT) + self.nodes[1].generatetoaddress(1, self.nodes[1].getnewaddress()) + sync_blocks(self.nodes) + + # so on the last block both should not be allowed + assert_equal(self.nodes[1].getsidechaininfo()["current_fedpegscripts"], ["52", "52"]) + assert_equal(self.nodes[0].testmempoolaccept([pegout_tx])[0]["reject-reason"], ERR_MP_INVALID_PEGOUT) + assert_equal(self.nodes[0].testmempoolaccept([pegin_tx])[0]["reject-reason"], ERR_MP_INVALID_PEGIN) + + # then undo the last block and make sure we could have mined the pegin in the very last block + self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) + # first use the pegin tx created earlier + pegin_txid = self.nodes[0].sendrawtransaction(pegin_tx) + self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress()) + assert_equal(self.nodes[0].gettransaction(pegin_txid)["confirmations"], 1) + # make sure that using claimpegin directly also works + self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) + pegin_txid = self.nodes[0].claimpegin(peg_tx, proof, fund_info["claim_script"]) + self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress()) + assert_equal(self.nodes[0].gettransaction(pegin_txid)["confirmations"], 1) def run_test(self): self.test_legacy_params() @@ -448,6 +565,7 @@ def run_test(self): self.test_four_fifth_vote() self.test_all_vote() self.test_transition_mempool_eject() + self.test_valid_epochs() if __name__ == '__main__': DynaFedTest().main()