diff --git a/test/functional/fake_stake/base_test.py b/test/functional/fake_stake/base_test.py deleted file mode 100644 index 78d1e8b6392e9..0000000000000 --- a/test/functional/fake_stake/base_test.py +++ /dev/null @@ -1,326 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019 The PIVX developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -# -*- coding: utf-8 -*- - -from io import BytesIO -from struct import pack -from random import randint, choice -import time - -from test_framework.authproxy import JSONRPCException -from test_framework.blocktools import create_coinbase_pos, stake_block, stake_next_block, get_prevouts, make_txes, DUMMY_KEY -from test_framework.key import CECKey -from test_framework.messages import CTransaction, CTxOut, CTxIn, COutPoint, COIN, msg_block -from test_framework.mininode import network_thread_start -from test_framework.test_framework import PivxTestFramework -from test_framework.script import CScript, OP_CHECKSIG -from test_framework.util import hash256, bytes_to_hex_str, hex_str_to_bytes, connect_nodes_bi, p2p_port - -from .util import TestNode, create_transaction, dir_size -''' ------------------------------------------------------------------------- -PIVX_FakeStakeTest CLASS ---------------------------------------------------- - -General Test Class to be extended by individual tests for each attack test -''' -class PIVX_FakeStakeTest(PivxTestFramework): - - def set_test_params(self): - ''' Setup test environment - :param: - :return: - ''' - self.setup_clean_chain = True - self.num_nodes = 1 - - - def init_test(self): - ''' Initializes test parameters - :param: - :return: - ''' - title = "*** Starting %s ***" % self.__class__.__name__ - underline = "-" * len(title) - self.log.info("\n\n%s\n%s\n%s\n", title, underline, self.description) - # Global Test parameters (override in run_test) - self.DEFAULT_FEE = 0.1 - # Spam blocks to send in current test - self.NUM_BLOCKS = 30 - - # Setup the p2p connections and start up the network thread. - self.test_nodes = [] - for i in range(self.num_nodes): - self.test_nodes.append(TestNode()) - self.test_nodes[i].peer_connect('127.0.0.1', p2p_port(i)) - - network_thread_start() # Start up network handling in another thread - self.node = self.nodes[0] - - # Let the test nodes get in sync - for i in range(self.num_nodes): - self.test_nodes[i].wait_for_verack() - - - def run_test(self): - ''' Performs the attack of this test - run init_test first. - :param: - :return: - ''' - self.description = "" - self.init_test() - return - - - def spend_utxo(self, utxo, address_list): - ''' spend amount from previously unspent output to a provided address - :param utxo: (JSON) returned from listunspent used as input - addresslist: (string) destination address - :return: txhash: (string) tx hash if successful, empty string otherwise - ''' - try: - inputs = [{"txid":utxo["txid"], "vout":utxo["vout"]}] - out_amount = (float(utxo["amount"]) - self.DEFAULT_FEE)/len(address_list) - outputs = {} - for address in address_list: - outputs[address] = out_amount - spendingTx = self.node.createrawtransaction(inputs, outputs) - spendingTx_signed = self.node.signrawtransaction(spendingTx) - if spendingTx_signed["complete"]: - txhash = self.node.sendrawtransaction(spendingTx_signed["hex"]) - return txhash - else: - self.log.warning("Error: %s" % str(spendingTx_signed["errors"])) - return "" - except JSONRPCException as e: - self.log.error("JSONRPCException: %s" % str(e)) - return "" - - - def spend_utxos(self, utxo_list, address_list = []): - ''' spend utxos to provided list of addresses or 10 new generate ones. - :param utxo_list: (JSON list) returned from listunspent used as input - address_list: (string list) [optional] recipient PIVX addresses. if not set, - 10 new addresses will be generated from the wallet for each tx. - :return: txHashes (string list) tx hashes - ''' - txHashes = [] - - # If not given, get 10 new addresses from self.node wallet - if address_list == []: - for i in range(10): - address_list.append(self.node.getnewaddress()) - - for utxo in utxo_list: - try: - # spend current utxo to provided addresses - txHash = self.spend_utxo(utxo, address_list) - if txHash != "": - txHashes.append(txHash) - except JSONRPCException as e: - self.log.error("JSONRPCException: %s" % str(e)) - continue - return txHashes - - - def stake_amplification_step(self, utxo_list, address_list = []): - ''' spends a list of utxos providing the list of new outputs - :param utxo_list: (JSON list) returned from listunspent used as input - address_list: (string list) [optional] recipient PIVX addresses. - :return: new_utxos: (JSON list) list of new (valid) inputs after the spends - ''' - self.log.info("--> Stake Amplification step started with %d UTXOs", len(utxo_list)) - txHashes = self.spend_utxos(utxo_list, address_list) - num_of_txes = len(txHashes) - new_utxos = [] - if num_of_txes> 0: - self.log.info("Created %d transactions...Mining 2 blocks to include them..." % num_of_txes) - self.node.generate(2) - time.sleep(2) - new_utxos = self.node.listunspent() - - self.log.info("Amplification step produced %d new \"Fake Stake\" inputs:" % len(new_utxos)) - return new_utxos - - - - def stake_amplification(self, utxo_list, iterations, address_list = []): - ''' performs the "stake amplification" which gives higher chances at finding fake stakes - :param utxo_list: (JSON list) returned from listunspent used as input - iterations: (int) amount of stake amplification steps to perform - address_list: (string list) [optional] recipient PIVX addresses. - :return: all_inputs: (JSON list) list of all spent inputs - ''' - self.log.info("** Stake Amplification started with %d UTXOs", len(utxo_list)) - valid_inputs = utxo_list - all_inputs = [] - for i in range(iterations): - all_inputs = all_inputs + valid_inputs - old_inputs = valid_inputs - valid_inputs = self.stake_amplification_step(old_inputs, address_list) - self.log.info("** Stake Amplification ended with %d \"fake\" UTXOs", len(all_inputs)) - return all_inputs - - - - def sign_stake_tx(self, block, stake_in_value, fZPoS=False): - ''' signs a coinstake transaction - :param block: (CBlock) block with stake to sign - stake_in_value: (int) staked amount - fZPoS: (bool) zerocoin stake - :return: stake_tx_signed: (CTransaction) signed tx - ''' - self.block_sig_key = CECKey() - - if fZPoS: - self.log.info("Signing zPoS stake...") - # Create raw zerocoin stake TX (signed) - raw_stake = self.node.createrawzerocoinstake(block.prevoutStake) - stake_tx_signed_raw_hex = raw_stake["hex"] - # Get stake TX private key to sign the block with - stake_pkey = raw_stake["private-key"] - self.block_sig_key.set_compressed(True) - self.block_sig_key.set_secretbytes(bytes.fromhex(stake_pkey)) - - else: - # Create a new private key and get the corresponding public key - self.block_sig_key.set_secretbytes(hash256(pack('= current-randomRange - randomRange2: (int) if fRandomHeight=True, height is < current-randomRange2 - fDoubleSpend: (bool) if true, stake input is double spent in block.vtx - fMustPass: (bool) if true, the blocks must be stored on disk - fZPoS: (bool) stake the block with zerocoin - spending_utxo_list: (string list) utxos to use for spending - :return: err_msgs: (string list) reports error messages from the test - or an empty list if test is successful - ''' - # Create empty error messages list - err_msgs = [] - # Log initial datadir size - self.log_data_dir_size() - # Get latest block number and hash - block_count = self.node.getblockcount() - pastBlockHash = self.node.getblockhash(block_count) - randomCount = block_count - self.log.info("Current height: %d" % block_count) - for i in range(0, self.NUM_BLOCKS): - if i !=0: - self.log.info("Sent %d blocks out of %d" % (i, self.NUM_BLOCKS)) - - # if fRandomHeight=True get a random block number (in range) and corresponding hash - if fRandomHeight: - randomCount = randint(block_count - randomRange, block_count - randomRange2) - pastBlockHash = self.node.getblockhash(randomCount) - - # Make spam txes (if not provided, copy the staking input) to DUMMY_KEY address - if len(spending_utxo_list) == 0: - spending_utxo_list = staking_utxo_list - spending_prevouts = get_prevouts(self.node, spending_utxo_list) - block_txes = make_txes(self.node, spending_prevouts, DUMMY_KEY.get_pubkey()) - - # Get staking prevouts and stake block (use DUMMY_KEY "" to stake) - current_block_n = randomCount + 1 - stakeInputs = get_prevouts(self.node, staking_utxo_list, fZPoS, randomCount) - block = stake_block(current_block_n, pastBlockHash, self.node, - stakeInputs, None, "", block_txes, fDoubleSpend) - - # Log time and size of the block - block_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(block.nTime)) - block_size = len(block.serialize())/1000 - self.log.info("Sending block %d [%s...] - nTime: %s - Size (kb): %.2f", - current_block_n, block.hash[:7], block_time, block_size) - - # Try submitblock - var = self.node.submitblock(bytes_to_hex_str(block.serialize())) - time.sleep(1) - if (not fMustPass and var not in [None, "bad-txns-invalid-zpiv"]) or (fMustPass and var != "inconclusive"): - self.log.error("submitblock [fMustPass=%s] result: %s" % (str(fMustPass), str(var))) - err_msgs.append("submitblock %d: %s" % (current_block_n, str(var))) - - # Try sending the message block - msg = msg_block(block) - try: - self.test_nodes[0].handle_connect() - self.test_nodes[0].send_message(msg) - time.sleep(2) - block_ret = self.node.getblock(block.hash) - if not fMustPass and block_ret is not None: - self.log.error("Error, block stored in %s chain" % name) - err_msgs.append("getblock %d: result not None" % current_block_n) - if fMustPass: - if block_ret is None: - self.log.error("Error, block NOT stored in %s chain" % name) - err_msgs.append("getblock %d: result is None" % current_block_n) - else: - self.log.info("Good. Block IS stored on disk.") - - except JSONRPCException as e: - exc_msg = str(e) - if exc_msg == "Can't read block from disk (-32603)": - if fMustPass: - self.log.warning("Bad! Block was NOT stored to disk.") - err_msgs.append(exc_msg) - else: - self.log.info("Good. Block was not stored on disk.") - else: - self.log.warning(exc_msg) - err_msgs.append(exc_msg) - - except Exception as e: - exc_msg = str(e) - self.log.error(exc_msg) - err_msgs.append(exc_msg) - - # Remove the used coinstake input - if fZPoS: - staking_utxo_list = [x for x in staking_utxo_list if - x['hash stake'] != block.prevoutStake[::-1].hex()] - else: - staking_utxo_list = [x for x in staking_utxo_list if - COutPoint( - int(x['txid'], 16), x['vout'] - ).serialize_uniqueness() != block.prevoutStake] - - self.log.info("Sent all %s blocks." % str(self.NUM_BLOCKS)) - # Log final datadir size - self.log_data_dir_size() - # Return errors list - return err_msgs diff --git a/test/functional/fake_stake/util.py b/test/functional/fake_stake/util.py deleted file mode 100644 index 5587d0984b78a..0000000000000 --- a/test/functional/fake_stake/util.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019 The PIVX developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -# -*- coding: utf-8 -*- - -import subprocess - -from test_framework.messages import CTransaction, CTxIn, CTxOut, COutPoint, COIN -from test_framework.messages import msg_getheaders, msg_headers, CBlockHeader -from test_framework.mininode import P2PInterface, mininode_lock -from test_framework.script import CScript -from test_framework.util import wait_until - - -''' ------------------------------------------------------------------------- -TestNode CLASS -------------------------------------------------------------- - -A peer we use to send messsages to pivxd and store responses -Extends P2PInterface. -''' - -# TestNode: A peer we use to send messages to bitcoind, and store responses. -class TestNode(P2PInterface): - def __init__(self): - super().__init__() - self.last_sendcmpct = [] - self.block_announced = False - # Store the hashes of blocks we've seen announced. - # This is for synchronizing the p2p message traffic, - # so we can eg wait until a particular block is announced. - self.announced_blockhashes = set() - - def on_sendcmpct(self, message): - self.last_sendcmpct.append(message) - - def on_cmpctblock(self, message): - self.block_announced = True - self.last_message["cmpctblock"].header_and_shortids.header.calc_sha256() - self.announced_blockhashes.add(self.last_message["cmpctblock"].header_and_shortids.header.sha256) - - def on_headers(self, message): - self.block_announced = True - for x in self.last_message["headers"].headers: - x.calc_sha256() - self.announced_blockhashes.add(x.sha256) - - def on_inv(self, message): - for x in self.last_message["inv"].inv: - if x.type == 2: - self.block_announced = True - self.announced_blockhashes.add(x.hash) - - # Requires caller to hold mininode_lock - def received_block_announcement(self): - return self.block_announced - - def clear_block_announcement(self): - with mininode_lock: - self.block_announced = False - self.last_message.pop("inv", None) - self.last_message.pop("headers", None) - self.last_message.pop("cmpctblock", None) - - def get_headers(self, locator, hashstop): - msg = msg_getheaders() - msg.locator.vHave = locator - msg.hashstop = hashstop - self.connection.send_message(msg) - - def send_header_for_blocks(self, new_blocks): - headers_message = msg_headers() - headers_message.headers = [CBlockHeader(b) for b in new_blocks] - self.send_message(headers_message) - - def request_headers_and_sync(self, locator, hashstop=0): - self.clear_block_announcement() - self.get_headers(locator, hashstop) - wait_until(self.received_block_announcement, timeout=30, lock=mininode_lock) - self.clear_block_announcement() - - # Block until a block announcement for a particular block hash is received. - def wait_for_block_announcement(self, block_hash, timeout=30): - def received_hash(): - return (block_hash in self.announced_blockhashes) - wait_until(received_hash, timeout=timeout, lock=mininode_lock) - - def send_await_disconnect(self, message, timeout=30): - """Sends a message to the node and wait for disconnect. - - This is used when we want to send a message into the node that we expect - will get us disconnected, eg an invalid block.""" - self.send_message(message) - wait_until(lambda: not self.connected, timeout=timeout, lock=mininode_lock) - - - -''' ------------------------------------------------------------------------- -MISC METHODS ---------------------------------------------------------------- -''' - -def dir_size(path): - ''' returns the size in bytes of the directory at given path - ''' - size = subprocess.check_output(['du','-shk', path]).split()[0].decode('utf-8') - return int(size) - - -def create_transaction(outPoint, sig, value, nTime, scriptPubKey=CScript()): - ''' creates a CTransaction object provided input-output data - ''' - tx = CTransaction() - tx.vin.append(CTxIn(outPoint, sig, 0xffffffff)) - tx.vout.append(CTxOut(value, scriptPubKey)) - tx.nTime = nTime - tx.calc_sha256() - return tx - diff --git a/test/functional/p2p_pos_doublespend.py b/test/functional/p2p_pos_doublespend.py deleted file mode 100755 index 0805ff0e014ad..0000000000000 --- a/test/functional/p2p_pos_doublespend.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019 The PIVX developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. - -''' -Covers the scenario of a valid PoS block with a valid coinstake transaction where the -coinstake input prevout is double spent in one of the other transactions in the same block. -''' - -from time import sleep - -from fake_stake.base_test import PIVX_FakeStakeTest - - -class PoSDoubleSpend(PIVX_FakeStakeTest): - - def run_test(self): - self.description = "Covers the scenario of a valid PoS block with a valid coinstake transaction where the coinstake input prevout is double spent in one of the other transactions in the same block." - self.init_test() - INITAL_MINED_BLOCKS = 300 - FORK_DEPTH = 30 - self.NUM_BLOCKS = 3 - - # 1) Starting mining blocks - self.log.info("Mining %d blocks.." % INITAL_MINED_BLOCKS) - self.node.generate(INITAL_MINED_BLOCKS) - - # 2) Collect the possible prevouts - self.log.info("Collecting all unspent coins which we generated from mining...") - staking_utxo_list = self.node.listunspent() - - # 3) Spam Blocks on the main chain - self.log.info("-- Main chain blocks first") - self.test_spam("Main", - staking_utxo_list, - fDoubleSpend=True, - spending_utxo_list=staking_utxo_list) - sleep(2) - - # 4) Mine some block as buffer - self.log.info("Mining %d more blocks..." % FORK_DEPTH) - self.node.generate(FORK_DEPTH) - sleep(2) - - # 5) Spam Blocks on a forked chain - self.log.info("-- Forked chain blocks now") - err_msgs = self.test_spam("Forked", - staking_utxo_list, - fRandomHeight=True, - randomRange=FORK_DEPTH, - fDoubleSpend=True, - spending_utxo_list=staking_utxo_list) - - if not len(err_msgs) == 0: - self.log.error("result: " + " | ".join(err_msgs)) - raise AssertionError("TEST FAILED") - - self.log.info("%s PASSED" % self.__class__.__name__) - -if __name__ == '__main__': - PoSDoubleSpend().main() diff --git a/test/functional/p2p_pos_fakestake.py b/test/functional/p2p_pos_fakestake.py deleted file mode 100755 index 2997cdae01a71..0000000000000 --- a/test/functional/p2p_pos_fakestake.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019 The PIVX developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. - -''' -Covers the scenario of a PoS block where the coinstake input prevout is already spent. -''' - -from time import sleep - -from test_framework.blocktools import get_prevouts, stake_next_block, stake_block -from fake_stake.base_test import PIVX_FakeStakeTest -from test_framework.util import bytes_to_hex_str - -class PoSFakeStake(PIVX_FakeStakeTest): - - def run_test(self): - self.description = "Covers the scenario of a PoS block where the coinstake input prevout is already spent." - self.init_test() - - INITAL_MINED_BLOCKS = 150 # First mined blocks (rewards collected to spend) - MORE_MINED_BLOCKS = 100 # Blocks mined after spending - STAKE_AMPL_ROUNDS = 2 # Rounds of stake amplification - self.NUM_BLOCKS = 3 # Number of spammed blocks - - # 1) Starting mining blocks - self.log.info("Mining %d blocks.." % INITAL_MINED_BLOCKS) - self.node.generate(INITAL_MINED_BLOCKS) - - # 2) Collect the possible prevouts - self.log.info("Collecting all unspent coins which we generated from mining...") - - # 3) Create 10 addresses - Do the stake amplification - self.log.info("Performing the stake amplification (%d rounds)..." % STAKE_AMPL_ROUNDS) - utxo_list = self.node.listunspent() - address_list = [] - for i in range(10): - address_list.append(self.node.getnewaddress()) - utxo_list = self.stake_amplification(utxo_list, STAKE_AMPL_ROUNDS, address_list) - - self.log.info("Done. Utxo list has %d elements." % len(utxo_list)) - sleep(2) - - # 4) Start mining again so that spent prevouts get confirmted in a block. - self.log.info("Mining %d more blocks..." % MORE_MINED_BLOCKS) - self.node.generate(MORE_MINED_BLOCKS) - sleep(2) - - # 5) Create "Fake Stake" blocks and send them - self.log.info("Creating Fake stake blocks") - err_msgs = self.test_spam("Main", utxo_list) - if not len(err_msgs) == 0: - self.log.error("result: " + " | ".join(err_msgs)) - raise AssertionError("TEST FAILED") - - self.log.info("%s PASSED" % self.__class__.__name__) - -if __name__ == '__main__': - PoSFakeStake().main() diff --git a/test/functional/p2p_pos_fakestake_accepted.py b/test/functional/p2p_pos_fakestake_accepted.py deleted file mode 100755 index 245e12e34ced7..0000000000000 --- a/test/functional/p2p_pos_fakestake_accepted.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019 The PIVX developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. - -''' -Covers the scenario of a valid PoS block where the coinstake input prevout is spent on main chain, -but not on the fork branch. These blocks must be accepted. -''' - -from time import sleep - -from fake_stake.base_test import PIVX_FakeStakeTest - -class PoSFakeStakeAccepted(PIVX_FakeStakeTest): - - def run_test(self): - self.description = "Covers the scenario of a valid PoS block where the coinstake input prevout is spent on main chain, but not on the fork branch. These blocks must be accepted." - self.init_test() - - INITAL_MINED_BLOCKS = 250 # First mined blocks - FORK_DEPTH = 50 # number of blocks after INITIAL_MINED_BLOCKS before the coins are spent - self.NUM_BLOCKS = 3 # Number of spammed blocks - - # 1) Starting mining blocks - self.log.info("Mining %d blocks.." % INITAL_MINED_BLOCKS) - self.node.generate(INITAL_MINED_BLOCKS) - - # 2) Collect the utxos generated on first 50 blocks and lock them - self.log.info("Collecting all unspent coins which we generated from mining...") - staking_utxo_list = self.node.listunspent(INITAL_MINED_BLOCKS-50+1) - sleep(2) - assert(self.node.lockunspent(False, [{"txid": x['txid'], "vout": x['vout']} for x in staking_utxo_list])) - - # 3) Mine more blocks - self.log.info("Mining %d more blocks.." % FORK_DEPTH) - self.node.generate(FORK_DEPTH) - sleep(2) - - # 4) Spend the coins collected in 2 (mined in the first 50 blocks) - assert (self.node.lockunspent(True, [{"txid": x['txid'], "vout": x['vout']} for x in staking_utxo_list])) - self.log.info("Spending the coins mined in the first 50 blocks...") - tx_hashes = self.spend_utxos(staking_utxo_list) - self.log.info("Spent %d transactions" % len(tx_hashes)) - sleep(2) - - # 5) Mine more blocks - self.node.generate(1) - sleep(2) - - # 6) Create "Fake Stake" blocks and send them - self.log.info("Creating Fake stake blocks") - err_msgs = self.test_spam("Fork", - staking_utxo_list, - fRandomHeight=True, - randomRange=FORK_DEPTH, - randomRange2=1, - fMustPass=True) - if not len(err_msgs) == 0: - self.log.error("result: " + " | ".join(err_msgs)) - raise AssertionError("TEST FAILED") - - self.log.info("%s PASSED" % self.__class__.__name__) - -if __name__ == '__main__': - PoSFakeStakeAccepted().main() diff --git a/test/functional/p2p_zpos_fakestake.py b/test/functional/p2p_zpos_fakestake.py deleted file mode 100755 index 6db63a42d46ca..0000000000000 --- a/test/functional/p2p_zpos_fakestake.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019 The PIVX developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. - -''' -Covers the scenario of a zPoS block where the coinstake input is a zerocoin spend -of an already spent coin. -''' - -from time import sleep - -from test_framework.authproxy import JSONRPCException - -from fake_stake.base_test import PIVX_FakeStakeTest - -class zPoSFakeStake(PIVX_FakeStakeTest): - - def run_test(self): - self.description = "Covers the scenario of a zPoS block where the coinstake input is a zerocoin spend of an already spent coin." - self.init_test() - - DENOM_TO_USE = 5000 # zc denomination - INITAL_MINED_BLOCKS = 330 # First mined blocks (rewards collected to mint) - MORE_MINED_BLOCKS = 25 # More blocks mined before spending zerocoins - self.NUM_BLOCKS = 2 # Number of spammed blocks - - # 1) Starting mining blocks - self.log.info("Mining %d blocks to get to zPOS activation...." % INITAL_MINED_BLOCKS) - self.node.generate(INITAL_MINED_BLOCKS) - sleep(2) - - # 2) Collect the possible prevouts and mint zerocoins with those - self.log.info("Collecting all unspent coins which we generated from mining...") - balance = self.node.getbalance("*", 100) - self.log.info("Minting zerocoins...") - initial_mints = 0 - while balance > DENOM_TO_USE: - try: - self.node.mintzerocoin(DENOM_TO_USE) - except JSONRPCException: - break - sleep(1) - initial_mints += 1 - self.node.generate(1) - sleep(1) - - if initial_mints % 5 == 0: - self.log.info("Minted %d coins" % initial_mints) - if initial_mints >= 70: - break - balance = self.node.getbalance("*", 100) - self.log.info("Minted %d coins in the %d-denom, remaining balance %d", initial_mints, DENOM_TO_USE, balance) - sleep(2) - - # 3) mine more blocks - self.log.info("Mining %d more blocks ... and getting spendable zerocoins" % MORE_MINED_BLOCKS) - self.node.generate(MORE_MINED_BLOCKS) - sleep(2) - mints = self.node.listmintedzerocoins(True, True) - mints_hashes = [x["serial hash"] for x in mints] - - # This mints are not ready spendable, only few of them. - self.log.info("Got %d confirmed mints" % len(mints_hashes)) - - # 4) spend mints - self.log.info("Spending mints in block %d..." % self.node.getblockcount()) - spends = 0 - spent_mints = [] - for mint in mints_hashes: - # create a single element list to pass to RPC spendzerocoinmints - mint_arg = [] - mint_arg.append(mint) - try: - self.node.spendzerocoinmints(mint_arg, "", False) - sleep(1) - spends += 1 - spent_mints.append(mint) - except JSONRPCException as e: - self.log.warning(str(e)) - continue - sleep(1) - self.log.info("Successfully spent %d mints" % spends) - - # 5) Start mining again so that spends get confirmed in a block. - self.log.info("Mining 5 more blocks...") - self.node.generate(5) - sleep(2) - - # 6) Collect some prevouts for random txes - self.log.info("Collecting inputs for txes...") - spending_utxo_list = self.node.listunspent() - sleep(1) - - # 7) Create "Fake Stake" blocks and send them - self.log.info("Creating Fake stake zPoS blocks...") - err_msgs = self.test_spam("Main", mints, spending_utxo_list=spending_utxo_list, fZPoS=True) - - if not len(err_msgs) == 0: - self.log.error("result: " + " | ".join(err_msgs)) - raise AssertionError("TEST FAILED") - - self.log.info("%s PASSED" % self.__class__.__name__) - -if __name__ == '__main__': - zPoSFakeStake().main() diff --git a/test/functional/p2p_zpos_fakestake_accepted.py b/test/functional/p2p_zpos_fakestake_accepted.py deleted file mode 100755 index 7e368fd08fedb..0000000000000 --- a/test/functional/p2p_zpos_fakestake_accepted.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019 The PIVX developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. - -''' -Performs the same check as in Test_02 verifying that zPoS forked blocks that stake a zerocoin which is spent on mainchain on an higher block are still accepted. -''' - -from test_framework.authproxy import JSONRPCException -from fake_stake.base_test import PIVX_FakeStakeTest -from time import sleep - -class zPoSFakeStakeAccepted(PIVX_FakeStakeTest): - - - def set_test_params(self): - ''' Setup test environment - :param: - :return: - ''' - self.setup_clean_chain = True - self.num_nodes = 1 - self.extra_args = [['-staking=1', '-debug=net', '-zpivstake']] * self.num_nodes - - def run_test(self): - self.description = "Performs the same check as in Test_02 verifying that zPoS forked blocks that stake a zerocoin which is spent on mainchain on an higher block are still accepted." - self.init_test() - - DENOM_TO_USE = 1000 # zc denomination - INITAL_MINED_BLOCKS = 321 - MORE_MINED_BLOCKS = 301 - FORK_DEPTH = 75 - self.NUM_BLOCKS = 2 - - # 1) Starting mining blocks - self.log.info("Mining %d blocks to get to zPOS activation...." % INITAL_MINED_BLOCKS) - self.node.generate(INITAL_MINED_BLOCKS) - sleep(2) - - # 2) Collect the possible prevouts and mint zerocoins with those - self.log.info("Collecting all unspent coins which we generated from mining...") - balance = self.node.getbalance("*", 100) - self.log.info("Minting zerocoins...") - initial_mints = 0 - while balance > DENOM_TO_USE: - try: - self.node.mintzerocoin(DENOM_TO_USE) - except JSONRPCException: - break - sleep(1) - initial_mints += 1 - self.node.generate(1) - sleep(1) - - if initial_mints % 5 == 0: - self.log.info("Minted %d coins" % initial_mints) - if initial_mints >= 20: - break - balance = self.node.getbalance("*", 100) - self.log.info("Minted %d coins in the %d-denom, remaining balance %d", initial_mints, DENOM_TO_USE, balance) - sleep(2) - - # 3) mine more blocks - self.log.info("Mining %d more blocks ... and getting spendable zerocoins" % MORE_MINED_BLOCKS) - self.node.generate(MORE_MINED_BLOCKS) - sleep(2) - mints = self.node.listmintedzerocoins(True, True) - sleep(1) - mints_hashes = [x["serial hash"] for x in mints] - - # This mints are not ready spendable, only few of them. - self.log.info("Got %d confirmed mints" % len(mints_hashes)) - - # 4) Start mining again so that spends get confirmed in a block. - self.log.info("Mining 200 more blocks...") - self.node.generate(200) - sleep(2) - - # 5) spend mints - self.log.info("Spending mints in block %d..." % self.node.getblockcount()) - spends = 0 - for mint in mints_hashes: - # create a single element list to pass to RPC spendzerocoinmints - mint_arg = [] - mint_arg.append(mint) - try: - self.node.spendzerocoinmints(mint_arg) - sleep(1) - spends += 1 - except JSONRPCException as e: - self.log.warning(str(e)) - continue - sleep(1) - self.log.info("Successfully spent %d mints" % spends) - - self.log.info("Mining 6 more blocks...") - self.node.generate(6) - sleep(2) - - # 6) Collect some prevouts for random txes - self.log.info("Collecting inputs for txes...") - utxo_list = self.node.listunspent() - sleep(1) - - # 7) Create valid forked zPoS blocks and send them - self.log.info("Creating stake zPoS blocks...") - err_msgs = self.test_spam("Fork", mints, spending_utxo_list=utxo_list, fZPoS=True, fRandomHeight=True, randomRange=FORK_DEPTH, randomRange2=50, fMustPass=True) - - if not len(err_msgs) == 0: - self.log.error("result: " + " | ".join(err_msgs)) - raise AssertionError("TEST FAILED") - - self.log.info("%s PASSED" % self.__class__.__name__) - -if __name__ == '__main__': - zPoSFakeStakeAccepted().main() diff --git a/test/functional/pos_fakestake.py b/test/functional/pos_fakestake.py new file mode 100755 index 0000000000000..67da45536e84e --- /dev/null +++ b/test/functional/pos_fakestake.py @@ -0,0 +1,428 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The PIVX developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +""" +Covers various scenarios of PoS blocks where the coinstake input is already spent +(either in a previous block, in a "future" block, or in the same block being staked). +Two nodes: nodes[0] moves the chain and checks the spam blocks, nodes[1] sends them. +Spend txes sent from nodes[1] are received by nodes[0] +Start with the PoW chache: 200 blocks. +For each test, nodes[1] sends 3 blocks. + +At the beginning nodes[0] mines 50 blocks (201-250) to reach PoS activation. +After tests 1-3, before tests 4-6, nodes[0] sends five transactions (included +in block 261) to nodes[1]. +Each tx spends one 250-PIV utxo to send 240.06 PIV (so nodes[1] can later +mint 6 zerocoins from it: 100 + 100 + 10 + 10 + 10 + 10 + fee). +Then nodes[0] stakes 40 more blocks (261-300) to reach zPoS phase. + +** Test_1: +(Nodes[1] spams a PoS block on main chain.) +(Stake inputs spent on the same block being staked.) +--> starts at height 250 + - nodes[1] saves his mature utxos 'utxos_to_spend' at block 250 + - nodes[0] mines 5 blocks (251-255) to be used as buffer for adding the fork chain later + - nodes[1] spams 3 blocks with height 256 --> [REJECTED] +--> ends at height 255 + +** Test_2 +(Nodes[1] spams a PoS block on main chain.) +(Stake inputs spent earlier on main chain.) +--> starts at height 255 + - nodes[1] spends utxos_to_spend at block 256 + - nodes[0] mines 5 more blocks (256-260) to include the spends + - nodes[1] spams 3 blocks with height 261 --> [REJECTED] +--> ends at height 260 + +** Test_3: +(Nodes[1] spams PoS blocks on a forked chain.) +(Stake inputs spent later on main chain.) +--> starts at height 260 + - nodes[1] spams fork block with height 251 --> [ACCEPTED] + - nodes[1] spams fork block with height 252 + (using the same coinstake input as previous block) --> [REJECTED] + - nodes[1] spams fork block with height 252 + (using a different coinstake input) --> [ACCEPTED] +--> ends at height 260 + +** Test_4: +(Nodes[1] spams a zPoS blocks on main chain.) +(Staked zerocoins spent on the same block being staked.) +--> starts at height 300 + - nodes[0] stakes 5 blocks (301-305) and, at each block, nodes[1] mints 6 zerocoins + - nodes[0] stakes 35 more blocks (306-340), so the minted zerocoins mature + - nodes[1] saves his mature coins 'coins_to_spend' at block 340 + - nodes[0] stakes 5 blocks (341-345) to be used as buffer for adding the fork chain later + - nodes[1] spams 3 blocks with height 346 --> [REJECTED] +--> ends at height 345 + +** Test_5: +(Nodes[1] spams a zPoS block on main chain.) +(Staked zerocoins spent earlier on main chain.) +--> starts at height 345 + - nodes[1] spends coins_to_spend at block 346 + - nodes[0] mines 5 more blocks (346-350) to include the spends + - nodes[1] spams 3 blocks with height 351 --> [REJECTED] +--> ends at height 350 + +** Test_6: +(Nodes[1] spams zPoS blocks on a forked chain.) +(Staked zerocoins spent later on main chain.) +--> starts at height 350 + - nodes[1] spams fork block with height 341 --> [ACCEPTED] + - nodes[1] spams fork block with height 342 + (using the same coinstake input as previous block) --> [REJECTED] + - nodes[1] spams fork block with height 342 + (using a different coinstake input) --> [ACCEPTED] +--> ends at height 350 +""" + +from io import BytesIO +from time import sleep + +from test_framework.authproxy import JSONRPCException +from test_framework.messages import COutPoint +from test_framework.test_framework import PivxTestFramework +from test_framework.util import ( + sync_blocks, + assert_equal, + assert_raises_rpc_error, + bytes_to_hex_str, + hash256, + set_node_times, + DecimalAmt +) + + +class FakeStakeTest(PivxTestFramework): + def set_test_params(self): + self.num_nodes = 2 + # nodes[0] moves the chain and checks the spam blocks, nodes[1] sends them + self.extra_args = [['-staking=0']]*self.num_nodes + + def setup_chain(self): + # Start with PoW cache: 200 blocks + self._initialize_chain() + self.enable_mocktime() + + def log_title(self): + title = "*** Starting %s ***" % self.__class__.__name__ + underline = "-" * len(title) + description = "Tests the 'fake stake' scenarios.\n" \ + "1) Stake on main chain with coinstake input spent on the same block\n" \ + "2) Stake on main chain with coinstake input spent on a previous block\n" \ + "3) Stake on a fork chain with coinstake input spent (later) in main chain\n" \ + "4) zPoS stake on main chain with zpiv coinstake input spent on the same block\n" \ + "5) zPoS stake on main chain with zpiv coinstake input spent on a previous block\n" \ + "6) zPoS stake on a fork chain with zpiv coinstake input spent (later) in main chain" + self.log.info("\n\n%s\n%s\n%s\n", title, underline, description) + + + def run_test(self): + # init custom fields + self.mocktime -= (131 * 60) + self.recipient_0 = self.nodes[0].getnewaddress() + self.recipient_1 = self.nodes[1].getnewaddress() + self.init_dummy_key() + + # start test + self.log_title() + set_node_times(self.nodes, self.mocktime) + + # nodes[0] mines 50 blocks (201-250) to reach PoS activation + self.log.info("Mining 50 blocks to reach PoS phase...") + for i in range(50): + self.mocktime = self.generate_pow(0, self.mocktime) + sync_blocks(self.nodes) + + # Check Tests 1-3 + self.test_1() + self.test_2() + self.test_3() + + # nodes[0] sends five transactions (included in block 261) to nodes[1]. + print() # add blank line without log indent + self.log.info("** Fake Stake - Intermission") + self.log.info("Sending 5 txes from nodes[0] to nodes[1]...") + for i in range(5): + self.log.info("%d) %s..." % (i, self.nodes[0].sendtoaddress(self.recipient_1, 240.06)[:16])) + + # Then nodes[0] stakes 40 more blocks (261-300) to reach zPIV phase + self.log.info("Staking 40 blocks to reach zPoS phase...") + for i in range(40): + self.mocktime = self.generate_pos(0, self.mocktime) + sync_blocks(self.nodes) + + # Check Tests 4-6 + self.test_4() + self.test_5() + self.test_6() + + + # ** PoS block - cstake input spent on the same block + def test_1(self): + print() # add blank line without log indent + self.log.info("** Fake Stake - Test_1") + + # nodes[1] saves his mature utxos 'utxos_to_spend' at block 250 + assert_equal(self.nodes[1].getblockcount(), 250) + self.utxos_to_spend = self.nodes[1].listunspent() + assert_equal(len(self.utxos_to_spend), 50) + self.log.info("50 'utxos_to_spend' collected.") + + # nodes[0] mines 5 blocks (251-255) to be used as buffer for adding the fork chain later + self.log.info("Mining 5 blocks as fork depth...") + for i in range(5): + self.mocktime = self.generate_pow(0, self.mocktime) + sync_blocks(self.nodes) + + # nodes[1] spams 3 blocks with height 256 --> [REJECTED] + assert_equal(self.nodes[1].getblockcount(), 255) + self.fake_stake(list(self.utxos_to_spend), fDoubleSpend=True) + self.log.info("--> Test_1 passed") + + # ** PoS block - cstake input spent in the past + def test_2(self): + print() # add blank line without log indent + self.log.info("** Fake Stake - Test_2") + + # nodes[1] spends utxos_to_spend at block 256 + assert_equal(self.nodes[1].getblockcount(), 255) + txid = self.spend_utxos(1, self.utxos_to_spend, self.recipient_0)[0] + self.log.info("'utxos_to_spend' spent on txid=(%s...) on block 256" % txid[:16]) + self.sync_all() + + # nodes[0] mines 5 more blocks (256-260) to include the spends + self.log.info("Mining 5 blocks to include the spends...") + for i in range(5): + self.mocktime = self.generate_pow(0, self.mocktime) + sync_blocks(self.nodes) + self.check_tx_in_chain(0, txid) + assert_equal(self.nodes[1].getbalance(), 0) + + # nodes[1] spams 3 blocks with height 261 --> [REJECTED] + assert_equal(self.nodes[1].getblockcount(), 260) + self.fake_stake(list(self.utxos_to_spend)) + self.log.info("--> Test_2 passed") + + # ** PoS block - cstake input spent in the future + def test_3(self): + print() # add blank line without log indent + self.log.info("** Fake Stake - Test_3") + + # nodes[1] spams fork block with height 251 --> [ACCEPTED] + # nodes[1] spams fork block with height 252 + # (using the same coinstake input as previous block) --> [REJECTED] + # nodes[1] spams fork block with height 252 + # (using a different coinstake input) --> [ACCEPTED] + assert_equal(self.nodes[1].getblockcount(), 260) + self.fake_stake(list(self.utxos_to_spend), nHeight=251) + self.log.info("--> Test_3 passed") + + # ** zPoS block - cstake zerocoin input spent on the same block + def test_4(self): + print() # add blank line without log indent + self.log.info("** Fake Stake - Test_4") + + # nodes[0] stakes 5 blocks (301-305) and, at each block, nodes[1] mints 6 zerocoins + self.log.info("Staking 5 blocks and minting zerocoins...") + for i in range(5): + self.nodes[1].mintzerocoin(240) + self.sync_all() + self.mocktime = self.generate_pos(0, self.mocktime) + sync_blocks(self.nodes) + + # nodes[0] stakes 35 more blocks (306-340), so the minted zerocoins mature + self.log.info("Staking 35 more blocks to mature the mints...") + for i in range(35): + self.mocktime = self.generate_pos(0, self.mocktime) + sync_blocks(self.nodes) + + # nodes[1] saves his mature coins 'coins_to_spend' at block 340 + assert_equal(self.nodes[1].getblockcount(), 340) + self.coins_to_spend = self.nodes[1].listmintedzerocoins(True, True) + assert_equal(len(self.coins_to_spend), 24) + self.log.info("24 mature 'coins_to_spend' collected.") + + # nodes[0] stakes 5 blocks (341-345) to be used as buffer for adding the fork chain later + self.log.info("Mining 5 blocks as fork depth...") + for i in range(5): + self.mocktime = self.generate_pos(0, self.mocktime) + sync_blocks(self.nodes) + + # nodes[1] spams blocks with height 346 --> [REJECTED] + assert_equal(self.nodes[1].getblockcount(), 345) + self.fake_stake(list(self.coins_to_spend), isZPoS=True, fDoubleSpend=True) + self.log.info("--> Test_4 passed") + + # ** zPoS block - cstake zerocoin input spent in the past + def test_5(self): + print() # add blank line without log indent + self.log.info("** Fake Stake - Test_5") + + # nodes[1] spends coins_to_spend at block 346 + assert_equal(self.nodes[1].getblockcount(), 345) + self.log.info("Spending coins_to_spend from nodes[1] to nodes[0] on block 346...") + txids = [] + for i in range(4): + txids.append(self.nodes[1].spendzerocoin(240, False, False, self.recipient_0, False)['txid']) + self.log.info("%d) %s..." % (i, txids[-1][:16])) + self.sync_all() + + # nodes[0] mines 5 more blocks (346-350) to include the spends + self.log.info("Mining 5 blocks to include the spends...") + for i in range(5): + self.mocktime = self.generate_pow(0, self.mocktime) + sync_blocks(self.nodes) + for txid in txids: + self.check_tx_in_chain(0, txid) + + # nodes[1] spams blocks with height 351 --> [REJECTED] + assert_equal(self.nodes[1].getblockcount(), 350) + self.fake_stake(list(self.coins_to_spend), isZPoS=True) + self.log.info("--> Test_5 passed") + + # ** zPoS block - cstake zerocoin input spent in the future + def test_6(self): + print() # add blank line without log indent + self.log.info("** Fake Stake - Test_6") + + # nodes[1] spams fork block with height 341 --> [ACCEPTED] + # nodes[1] spams fork block with height 342 + # (using the same coinstake input as previous block) --> [REJECTED] + # nodes[1] spams fork block with height 342 + # (using a different coinstake input) --> [ACCEPTED] + assert_equal(self.nodes[1].getblockcount(), 350) + self.fake_stake(list(self.coins_to_spend), isZPoS=True, nHeight=341) + self.log.info("--> Test_6 passed") + + + def fake_stake(self, + staking_utxo_list, + nHeight=-1, + isZPoS=False, + fDoubleSpend=False): + """ General method to create, send and test the spam blocks + :param staking_utxo_list: (string list) utxos to use for staking + nHeight: (int, optional) height of the staked block. + Used only for fork chain. In main chain it's current height + 1 + isZPoS: (bool) stake the block with zerocoin inputs for coinstake + fDoubleSpend: (bool) if true, stake input is double spent in block.vtx + :return: + """ + # Get block number, block time and prevBlock hash + currHeight = self.nodes[1].getblockcount() + isMainChain = (nHeight == -1) + chainName = "main" if isMainChain else "forked" + nTime = self.mocktime + if isMainChain: + nHeight = currHeight + 1 + prevBlockHash = self.nodes[1].getblockhash(nHeight - 1) + nTime += (nHeight - currHeight) * 60 + + # New block hash, coinstake input and list of txes + bHash = None + stakedUtxo = None + + # For each test, send three blocks. + # On main chain they are all the same height. + # On fork chain, send three blocks where both the second and third block sent, + # are built on top of the first one. + for i in range(3): + fMustBeAccepted = (not isMainChain and i != 1) + block_txes = [] + + # update block number and prevBlock hash on second block sent on forked chain + if not isMainChain and i == 1: + nHeight += 1 + nTime += 60 + prevBlockHash = bHash + + stakeInputs = self.get_prevouts(1, staking_utxo_list, isZPoS, nHeight - 1) + # Update stake inputs for second block sent on forked chain (must stake the same input) + if not isMainChain and i == 1: + stakeInputs = self.get_prevouts(1, [stakedUtxo], isZPoS, nHeight-1) + + # Make spam txes sending the inputs to DUMMY_KEY in order to test double spends + if fDoubleSpend: + spending_prevouts = self.get_prevouts(1, staking_utxo_list, isZPoS) + block_txes = self.make_txes(1, spending_prevouts, self.DUMMY_KEY.get_pubkey()) + + # Stake the spam block + block = self.stake_block(1, nHeight, prevBlockHash, stakeInputs, + nTime, "", block_txes, fDoubleSpend) + # Log stake input + if not isZPoS: + prevout = COutPoint() + prevout.deserialize_uniqueness(BytesIO(block.prevoutStake)) + self.log.info("Staked input: [%s...-%s]" % ('{:x}'.format(prevout.hash)[:12], prevout.n)) + else: + self.log.info("Staked coin with serial hash [%s...]" % block.prevoutStake.hex()[:16]) + + # Try submitblock and check result + self.log.info("Trying to send block [%s...] with height=%d" % (block.hash[:16], nHeight)) + var = self.nodes[1].submitblock(bytes_to_hex_str(block.serialize())) + sleep(1) + if (isZPoS and not isMainChain and i < 2): + # !TODO: fix this last case failing (this must NOT be accepted) + fMustBeAccepted = True + if (not fMustBeAccepted and var not in [None, "rejected", "bad-txns-invalid-zpiv"]): + raise AssertionError("Error, block submitted (%s) in %s chain" % (var, chainName)) + elif (fMustBeAccepted and var != "inconclusive"): + raise AssertionError("Error, block not submitted (%s) in %s chain" % (var, chainName)) + self.log.info("Done. Updating context...") + + # Sync and check block hash + bHash = block.hash + self.checkBlockHash(bHash, fMustBeAccepted) + + # Update curr block data + if isZPoS: + stakedUtxo = [x for x in staking_utxo_list if + x['hash stake'] == block.prevoutStake[::-1].hex()][0] + else: + stakedUtxo = [x for x in staking_utxo_list if COutPoint( + int(x['txid'], 16), x['vout']).serialize_uniqueness() == block.prevoutStake][0] + + # Remove the used coinstake input (except before second block on fork chain) + if isMainChain or i != 0: + staking_utxo_list.remove(stakedUtxo) + + self.log.info("All blocks sent") + + + def checkBlockHash(self, bHash, fMustBeAccepted): + + def strBlockCheck(bHash, fAccepted=True, fError=False): + return "%s Block [%s...] IS %sstored on disk" % ( + "Error!" if fError else "Good.", bHash[:16], "" if fAccepted else "NOT ") + try: + block_ret = self.nodes[1].getblock(bHash) + if not fMustBeAccepted: + if block_ret is not [None, None]: + self.log.warning(str(block_ret)) + raise AssertionError(strBlockCheck(bHash, True, True)) + else: + self.log.info(strBlockCheck(bHash, False, False)) + if fMustBeAccepted: + if None in block_ret: + self.log.warning(str(block_ret)) + raise AssertionError(strBlockCheck(bHash, False, True)) + else: + self.log.info(strBlockCheck(bHash, True, False)) + + except JSONRPCException as e: + exc_msg = str(e) + if exc_msg in ["Can't read block from disk (-32603)", "Block not found (-5)"]: + if fMustBeAccepted: + raise AssertionError(strBlockCheck(bHash, False, True)) + else: + self.log.info(strBlockCheck(bHash, False, False)) + else: + raise + + +if __name__ == '__main__': + FakeStakeTest().main() \ No newline at end of file diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 2bed8e677ee86..1dcbb8009eccc 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -65,17 +65,13 @@ 'rpc_rawtransaction.py', 'wallet_zapwallettxes.py', 'wallet_keypool_topup.py', - 'p2p_pos_doublespend.py', 'wallet_txn_doublespend.py --mineblock', 'wallet_txn_clone.py --mineblock', 'interface_rest.py', 'feature_proxy.py', - 'p2p_pos_fakestake.py', - 'p2p_pos_fakestake_accepted.py', + 'pos_fakestake.py', 'zerocoin_spends.py', - 'p2p_zpos_fakestake.py', - #'p2p_zpos_fakestake_accepted.py', #'zerocoin_wrapped_serials.py', #'feature_block.py', #'rpc_fundrawtransaction.py',