def solve(self, signblockprivkeys): # create signed blocks. sighash = self.getsighash() self.proof.clear() for privkey in signblockprivkeys: signKey = CECKey() signKey.set_secretbytes(hex_str_to_bytes(privkey)) signKey.set_compressed(True) sig = signKey.sign(sighash) self.proof.append(sig) self.rehash()
class redspace_FakeStakeTest(BitcoinTestFramework): 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']] * self.num_nodes def setup_network(self): ''' Can't rely on syncing all the nodes when staking=1 :param: :return: ''' self.setup_nodes() for i in range(self.num_nodes - 1): for j in range(i + 1, self.num_nodes): connect_nodes_bi(self.nodes, i, j) def init_test(self): ''' Initializes test parameters :param: :return: ''' self.log.info( "\n\n*** Starting %s ***\n------------------------\n%s\n", self.__class__.__name__, 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 create_spam_block(self, hashPrevBlock, stakingPrevOuts, height, fStakeDoubleSpent=False, fZPoS=False, spendingPrevOuts={}): ''' creates a block to spam the network with :param hashPrevBlock: (hex string) hash of previous block stakingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints (to be used as staking inputs) to amount, block_time, nStakeModifier, hashStake height: (int) block height fStakeDoubleSpent: (bool) spend the coinstake input inside the block fZPoS: (bool) stake the block with zerocoin spendingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints (to be used as tx inputs) to amount, block_time, nStakeModifier, hashStake :return block: (CBlock) generated block ''' self.log.info("Creating Spam Block") # If not given inputs to create spam txes, use a copy of the staking inputs if len(spendingPrevOuts) == 0: spendingPrevOuts = dict(stakingPrevOuts) # Get current time current_time = int(time.time()) nTime = current_time & 0xfffffff0 # Create coinbase TX # Even if PoS blocks have empty coinbase vout, the height is required for the vin script coinbase = create_coinbase(height) coinbase.vout[0].nValue = 0 coinbase.vout[0].scriptPubKey = b"" coinbase.nTime = nTime coinbase.rehash() # Create Block with coinbase block = create_block(int(hashPrevBlock, 16), coinbase, nTime) # Find valid kernel hash - Create a new private key used for block signing. if not block.solve_stake(stakingPrevOuts): raise Exception("Not able to solve for any prev_outpoint") self.log.info("Stake found. Signing block...") # Sign coinstake TX and add it to the block signed_stake_tx = self.sign_stake_tx( block, stakingPrevOuts[block.prevoutStake][0], fZPoS) block.vtx.append(signed_stake_tx) # Remove coinstake input prevout unless we want to try double spending in the same block. # Skip for zPoS as the spendingPrevouts are just regular UTXOs if not fZPoS and not fStakeDoubleSpent: del spendingPrevOuts[block.prevoutStake] # remove a random prevout from the list # (to randomize block creation if the same height is picked two times) del spendingPrevOuts[choice(list(spendingPrevOuts))] # Create spam for the block. Sign the spendingPrevouts self.log.info("Creating spam TXes...") for outPoint in spendingPrevOuts: value_out = int(spendingPrevOuts[outPoint][0] - self.DEFAULT_FEE * COIN) tx = create_transaction(outPoint, b"", value_out, nTime, scriptPubKey=CScript([ self.block_sig_key.get_pubkey(), OP_CHECKSIG ])) # sign txes signed_tx_hex = self.node.signrawtransaction( bytes_to_hex_str(tx.serialize()))['hex'] signed_tx = CTransaction() signed_tx.deserialize(BytesIO(hex_str_to_bytes(signed_tx_hex))) block.vtx.append(signed_tx) # Get correct MerkleRoot and rehash block block.hashMerkleRoot = block.calc_merkle_root() block.rehash() # Sign block with coinstake key and return it block.sign_block(self.block_sig_key) return block 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 redspace 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 redspace 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 redspace 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('<I', 0xffff))) pubkey = self.block_sig_key.get_pubkey() # Create the raw stake TX (unsigned) scriptPubKey = CScript([pubkey, OP_CHECKSIG]) outNValue = int(stake_in_value + 2 * COIN) stake_tx_unsigned = CTransaction() stake_tx_unsigned.nTime = block.nTime stake_tx_unsigned.vin.append(CTxIn(block.prevoutStake)) stake_tx_unsigned.vin[0].nSequence = 0xffffffff stake_tx_unsigned.vout.append(CTxOut()) stake_tx_unsigned.vout.append(CTxOut(outNValue, scriptPubKey)) # Sign the stake TX stake_tx_signed_raw_hex = self.node.signrawtransaction( bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex'] # Deserialize the signed raw tx into a CTransaction object and return it stake_tx_signed = CTransaction() stake_tx_signed.deserialize( BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))) return stake_tx_signed def get_prevouts(self, utxo_list, blockHeight, zpos=False): ''' get prevouts (map) for each utxo in a list :param utxo_list: <if zpos=False> (JSON list) utxos returned from listunspent used as input <if zpos=True> (JSON list) mints returned from listmintedzerocoins used as input blockHeight: (int) height of the previous block zpos: (bool) type of utxo_list :return: stakingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints to amount, block_time, nStakeModifier, hashStake ''' zerocoinDenomList = [1, 5, 10, 50, 100, 500, 1000, 5000] stakingPrevOuts = {} for utxo in utxo_list: if zpos: # get mint checkpoint checkpointHeight = blockHeight - 200 checkpointBlock = self.node.getblock( self.node.getblockhash(checkpointHeight), True) checkpoint = int(checkpointBlock['acc_checkpoint'], 16) # parse checksum and get checksumblock pos = zerocoinDenomList.index(utxo['denomination']) checksum = (checkpoint >> (32 * (len(zerocoinDenomList) - 1 - pos))) & 0xFFFFFFFF checksumBlock = self.node.getchecksumblock( hex(checksum), utxo['denomination'], True) # get block hash and block time txBlockhash = checksumBlock['hash'] txBlocktime = checksumBlock['time'] else: # get raw transaction for current input utxo_tx = self.node.getrawtransaction(utxo['txid'], 1) # get block hash and block time txBlocktime = utxo_tx['blocktime'] txBlockhash = utxo_tx['blockhash'] # get Stake Modifier stakeModifier = int( self.node.getblock(txBlockhash)['modifier'], 16) # assemble prevout object utxo_to_stakingPrevOuts(utxo, stakingPrevOuts, txBlocktime, stakeModifier, zpos) return stakingPrevOuts def log_data_dir_size(self): ''' Prints the size of the '/regtest/blocks' directory. :param: :return: ''' init_size = dir_size(self.node.datadir + "/regtest/blocks") self.log.info("Size of data dir: %s kilobytes" % str(init_size)) def test_spam(self, name, staking_utxo_list, fRandomHeight=False, randomRange=0, randomRange2=0, fDoubleSpend=False, fMustPass=False, fZPoS=False, spending_utxo_list=[]): ''' General method to create, send and test the spam blocks :param name: (string) chain branch (usually either "Main" or "Forked") staking_utxo_list: (string list) utxos to use for staking fRandomHeight: (bool) send blocks at random height randomRange: (int) if fRandomHeight=True, height is >= 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) # Get spending prevouts and staking prevouts for the height of current block current_block_n = randomCount + 1 stakingPrevOuts = self.get_prevouts(staking_utxo_list, randomCount, zpos=fZPoS) spendingPrevOuts = self.get_prevouts(spending_utxo_list, randomCount) # Create the spam block block = self.create_spam_block(pastBlockHash, stakingPrevOuts, current_block_n, fStakeDoubleSpent=fDoubleSpend, fZPoS=fZPoS, spendingPrevOuts=spendingPrevOuts) # 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-zrsc"] ) 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) 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
def create_block(self, prev_hash, staking_prevouts, height, node_n, s_address, fInvalid=0): api = self.nodes[node_n] # Get current time current_time = int(time.time()) nTime = current_time & 0xfffffff0 # Create coinbase TX coinbase = create_coinbase(height) coinbase.vout[0].nValue = 0 coinbase.vout[0].scriptPubKey = b"" coinbase.nTime = nTime coinbase.rehash() # Create Block with coinbase block = create_block(int(prev_hash, 16), coinbase, nTime) # Find valid kernel hash - Create a new private key used for block signing. if not block.solve_stake(staking_prevouts): raise Exception("Not able to solve for any prev_outpoint") # Create coinstake TX amount, prev_time, prevScript = staking_prevouts[block.prevoutStake] outNValue = int(amount + 250 * COIN) stake_tx_unsigned = CTransaction() stake_tx_unsigned.nTime = block.nTime stake_tx_unsigned.vin.append(CTxIn(block.prevoutStake)) stake_tx_unsigned.vin[0].nSequence = 0xffffffff stake_tx_unsigned.vout.append(CTxOut()) stake_tx_unsigned.vout.append( CTxOut(outNValue, hex_str_to_bytes(prevScript))) if fInvalid == 1: # Create a new private key and get the corresponding public key block_sig_key = CECKey() block_sig_key.set_secretbytes(hash256(pack('<I', 0xffff))) pubkey = block_sig_key.get_pubkey() stake_tx_unsigned.vout[1].scriptPubKey = CScript( [pubkey, OP_CHECKSIG]) else: # Export the staking private key to sign the block with it privKey, compressed = wif_to_privkey(api.dumpprivkey(s_address)) block_sig_key = CECKey() block_sig_key.set_compressed(compressed) block_sig_key.set_secretbytes(bytes.fromhex(privKey)) # check the address addy = key_to_p2pkh(bytes_to_hex_str(block_sig_key.get_pubkey()), False, True) assert (addy == s_address) if fInvalid == 2: # add a new output with 100 coins from the pot new_key = CECKey() new_key.set_secretbytes(hash256(pack('<I', 0xffff))) pubkey = new_key.get_pubkey() stake_tx_unsigned.vout.append( CTxOut(100 * COIN, CScript([pubkey, OP_CHECKSIG]))) stake_tx_unsigned.vout[1].nValue = outNValue - 100 * COIN # Sign coinstake TX and add it to the block stake_tx_signed_raw_hex = api.signrawtransaction( bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex'] stake_tx_signed = CTransaction() stake_tx_signed.deserialize( BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))) block.vtx.append(stake_tx_signed) # Get correct MerkleRoot and rehash block block.hashMerkleRoot = block.calc_merkle_root() block.rehash() # sign block with block signing key and return it block.sign_block(block_sig_key) return block
def make_transactions(self, txtype, num_txns, stxn_vin_size, create_double_spends=False): key = CECKey() key.set_secretbytes(b"horsebattery") key.set_compressed(True) # Each coin being spent will always result in at least 14 expensive ECDSA checks. # 0x7f03 33 OP_NUM2BIN creates a valid non-zero compressed pubkey. redeem_script = CScript([ OP_1, key.get_pubkey(), 0x7f03, 33, OP_NUM2BIN, OP_DUP, OP_2DUP, OP_2DUP, OP_2DUP, OP_3DUP, OP_3DUP, OP_15, OP_CHECKMULTISIG ]) # Calculate how many found txns are needed to create a required spend money txns (num_txns) # - a fund txns are of type 1 - N (N=vouts_size_per_fund_txn) # - a spend money txns are of type M-1 (M inputs & 1 output) def estimate_fund_txns_number(num_txns, vouts_size_per_fund_txn): fund_txns_num = 1 if num_txns >= vouts_size_per_fund_txn: if num_txns % vouts_size_per_fund_txn == 0: fund_txns_num = num_txns // vouts_size_per_fund_txn else: fund_txns_num = num_txns // vouts_size_per_fund_txn + 1 return fund_txns_num * vouts_size_per_fund_txn # Create funding transactions that will provide funds for other transcations def make_fund_txn(node, out_value, num_vout_txns): # Create fund txn ftx = CTransaction() for i in range(num_vout_txns): ftx.vout.append( CTxOut( out_value, CScript([OP_HASH160, hash160(redeem_script), OP_EQUAL]))) # fund the transcation: ftxHex = node.fundrawtransaction( ToHex(ftx), {'changePosition': len(ftx.vout)})['hex'] ftxHex = node.signrawtransaction(ftxHex)['hex'] ftx = FromHex(CTransaction(), ftxHex) ftx.rehash() return ftx, ftxHex # Create a spend txn def make_spend_txn(txtype, fund_txn_hash, fund_txn_num_vouts, out_value): # Create txn spend_tx = CTransaction() for idx in range(fund_txn_num_vouts): spend_tx.vin.append(CTxIn(COutPoint(fund_txn_hash, idx), b'')) sighash = SignatureHashForkId( redeem_script, spend_tx, idx, SIGHASH_ANYONECANPAY | SIGHASH_FORKID | SIGHASH_NONE, out_value) sig = key.sign(sighash) + bytes( bytearray([ SIGHASH_ANYONECANPAY | SIGHASH_FORKID | SIGHASH_NONE ])) spend_tx.vin[idx].scriptSig = CScript( [OP_0, sig, redeem_script]) # Standard transaction if TxType.standard == txtype: spend_tx.vout.append( CTxOut(out_value - 1000, CScript([OP_RETURN]))) # Non-standard transaction elif TxType.nonstandard == txtype: spend_tx.vout.append( CTxOut(out_value - 1000, CScript([OP_TRUE]))) spend_tx.rehash() return spend_tx # # Generate some blocks to have enough spendable coins # node = self.nodes[0] node.generate(101) # # Estimate a number of required fund txns # out_value = 2000 # Number of outputs in each fund txn fund_txn_num_vouts = stxn_vin_size fund_txns_num = estimate_fund_txns_number(num_txns, fund_txn_num_vouts) # # Create and send fund txns to the mempool # fund_txns = [] for i in range(fund_txns_num): ftx, ftxHex = make_fund_txn(node, out_value, fund_txn_num_vouts) node.sendrawtransaction(ftxHex) fund_txns.append(ftx) # Ensure that mempool is empty to avoid 'too-long-mempool-chain' errors in next test node.generate(1) # # Create spend transactions. # txtype_to_create = txtype spend_txs = [] for i in range(len(fund_txns)): # If standard and non-standard txns are required then create equal (in size) sets. if TxType.std_and_nonstd == txtype: if i % 2: txtype_to_create = TxType.standard else: txtype_to_create = TxType.nonstandard # Create a spend money txn with fund_txn_num_vouts number of inputs. spend_tx = make_spend_txn(txtype_to_create, fund_txns[i].sha256, fund_txn_num_vouts, out_value) # Create double spend txns if required if create_double_spends and len(spend_txs) < num_txns // 2: # The first half of the array are double spend txns spend_tx.vin.append( CTxIn( COutPoint(fund_txns[len(fund_txns) - i - 1].sha256, 0), b'')) sighash = SignatureHashForkId( redeem_script, spend_tx, stxn_vin_size, SIGHASH_ANYONECANPAY | SIGHASH_FORKID | SIGHASH_NONE, out_value) sig = key.sign(sighash) + bytes( bytearray([ SIGHASH_ANYONECANPAY | SIGHASH_FORKID | SIGHASH_NONE ])) spend_tx.vin[stxn_vin_size].scriptSig = CScript( [OP_0, sig, redeem_script]) spend_tx.rehash() spend_txs.append(spend_tx) return spend_txs
def print_wif_address(secret, compress=True): k = CECKey() k.set_secretbytes(bytes.fromhex(secret)) k.set_compressed(compress) pk = k.get_pubkey() print(k.get_wif(b'\x80'), key_to_p2pkh(pk, True))
class PIVX_FakeStakeTest(BitcoinTestFramework): 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']]*self.num_nodes def setup_network(self): ''' Can't rely on syncing all the nodes when staking=1 :param: :return: ''' self.setup_nodes() for i in range(self.num_nodes - 1): for j in range(i+1, self.num_nodes): connect_nodes_bi(self.nodes, i, j) def init_test(self): ''' Initializes test parameters :param: :return: ''' self.log.info("\n\n*** Starting %s ***\n------------------------\n%s\n", self.__class__.__name__, 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 create_spam_block(self, hashPrevBlock, stakingPrevOuts, height, fStakeDoubleSpent=False, fZPoS=False, spendingPrevOuts={}): ''' creates a block to spam the network with :param hashPrevBlock: (hex string) hash of previous block stakingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints (to be used as staking inputs) to amount, block_time, nStakeModifier, hashStake height: (int) block height fStakeDoubleSpent: (bool) spend the coinstake input inside the block fZPoS: (bool) stake the block with zerocoin spendingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints (to be used as tx inputs) to amount, block_time, nStakeModifier, hashStake :return block: (CBlock) generated block ''' self.log.info("Creating Spam Block") # If not given inputs to create spam txes, use a copy of the staking inputs if len(spendingPrevOuts) == 0: spendingPrevOuts = dict(stakingPrevOuts) # Get current time current_time = int(time.time()) nTime = current_time & 0xfffffff0 # Create coinbase TX # Even if PoS blocks have empty coinbase vout, the height is required for the vin script coinbase = create_coinbase(height) coinbase.vout[0].nValue = 0 coinbase.vout[0].scriptPubKey = b"" coinbase.nTime = nTime coinbase.rehash() # Create Block with coinbase block = create_block(int(hashPrevBlock, 16), coinbase, nTime) # Find valid kernel hash - Create a new private key used for block signing. if not block.solve_stake(stakingPrevOuts): raise Exception("Not able to solve for any prev_outpoint") self.log.info("Stake found. Signing block...") # Sign coinstake TX and add it to the block signed_stake_tx = self.sign_stake_tx(block, stakingPrevOuts[block.prevoutStake][0], fZPoS) block.vtx.append(signed_stake_tx) # Remove coinstake input prevout unless we want to try double spending in the same block. # Skip for zPoS as the spendingPrevouts are just regular UTXOs if not fZPoS and not fStakeDoubleSpent: del spendingPrevOuts[block.prevoutStake] # remove a random prevout from the list # (to randomize block creation if the same height is picked two times) del spendingPrevOuts[choice(list(spendingPrevOuts))] # Create spam for the block. Sign the spendingPrevouts self.log.info("Creating spam TXes...") for outPoint in spendingPrevOuts: value_out = int(spendingPrevOuts[outPoint][0] - self.DEFAULT_FEE * COIN) tx = create_transaction(outPoint, b"", value_out, nTime, scriptPubKey=CScript([self.block_sig_key.get_pubkey(), OP_CHECKSIG])) # sign txes signed_tx_hex = self.node.signrawtransaction(bytes_to_hex_str(tx.serialize()))['hex'] signed_tx = CTransaction() signed_tx.deserialize(BytesIO(hex_str_to_bytes(signed_tx_hex))) block.vtx.append(signed_tx) # Get correct MerkleRoot and rehash block block.hashMerkleRoot = block.calc_merkle_root() block.rehash() # Sign block with coinstake key and return it block.sign_block(self.block_sig_key) return block 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('<I', 0xffff))) pubkey = self.block_sig_key.get_pubkey() # Create the raw stake TX (unsigned) scriptPubKey = CScript([pubkey, OP_CHECKSIG]) outNValue = int(stake_in_value + 2*COIN) stake_tx_unsigned = CTransaction() stake_tx_unsigned.nTime = block.nTime stake_tx_unsigned.vin.append(CTxIn(block.prevoutStake)) stake_tx_unsigned.vin[0].nSequence = 0xffffffff stake_tx_unsigned.vout.append(CTxOut()) stake_tx_unsigned.vout.append(CTxOut(outNValue, scriptPubKey)) # Sign the stake TX stake_tx_signed_raw_hex = self.node.signrawtransaction(bytes_to_hex_str(stake_tx_unsigned.serialize()))['hex'] # Deserialize the signed raw tx into a CTransaction object and return it stake_tx_signed = CTransaction() stake_tx_signed.deserialize(BytesIO(hex_str_to_bytes(stake_tx_signed_raw_hex))) return stake_tx_signed def get_prevouts(self, utxo_list, blockHeight, zpos=False): ''' get prevouts (map) for each utxo in a list :param utxo_list: <if zpos=False> (JSON list) utxos returned from listunspent used as input <if zpos=True> (JSON list) mints returned from listmintedzerocoins used as input blockHeight: (int) height of the previous block zpos: (bool) type of utxo_list :return: stakingPrevOuts: ({COutPoint --> (int, int, int, str)} dictionary) map outpoints to amount, block_time, nStakeModifier, hashStake ''' zerocoinDenomList = [1, 5, 10, 50, 100, 500, 1000, 5000] stakingPrevOuts = {} for utxo in utxo_list: if zpos: # get mint checkpoint checkpointHeight = blockHeight - 200 checkpointBlock = self.node.getblock(self.node.getblockhash(checkpointHeight), True) checkpoint = int(checkpointBlock['acc_checkpoint'], 16) # parse checksum and get checksumblock pos = zerocoinDenomList.index(utxo['denomination']) checksum = (checkpoint >> (32 * (len(zerocoinDenomList) - 1 - pos))) & 0xFFFFFFFF checksumBlock = self.node.getchecksumblock(hex(checksum), utxo['denomination'], True) # get block hash and block time txBlockhash = checksumBlock['hash'] txBlocktime = checksumBlock['time'] else: # get raw transaction for current input utxo_tx = self.node.getrawtransaction(utxo['txid'], 1) # get block hash and block time txBlocktime = utxo_tx['blocktime'] txBlockhash = utxo_tx['blockhash'] # get Stake Modifier stakeModifier = int(self.node.getblock(txBlockhash)['modifier'], 16) # assemble prevout object utxo_to_stakingPrevOuts(utxo, stakingPrevOuts, txBlocktime, stakeModifier, zpos) return stakingPrevOuts def log_data_dir_size(self): ''' Prints the size of the '/regtest/blocks' directory. :param: :return: ''' init_size = dir_size(self.node.datadir + "/regtest/blocks") self.log.info("Size of data dir: %s kilobytes" % str(init_size)) def test_spam(self, name, staking_utxo_list, fRandomHeight=False, randomRange=0, randomRange2=0, fDoubleSpend=False, fMustPass=False, fZPoS=False, spending_utxo_list=[]): ''' General method to create, send and test the spam blocks :param name: (string) chain branch (usually either "Main" or "Forked") staking_utxo_list: (string list) utxos to use for staking fRandomHeight: (bool) send blocks at random height randomRange: (int) if fRandomHeight=True, height is >= 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) # Get spending prevouts and staking prevouts for the height of current block current_block_n = randomCount + 1 stakingPrevOuts = self.get_prevouts(staking_utxo_list, randomCount, zpos=fZPoS) spendingPrevOuts = self.get_prevouts(spending_utxo_list, randomCount) # Create the spam block block = self.create_spam_block(pastBlockHash, stakingPrevOuts, current_block_n, fStakeDoubleSpent=fDoubleSpend, fZPoS=fZPoS, spendingPrevOuts=spendingPrevOuts) # 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) 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
def run_test (self): self.nodes[2].importprivkey("cTnxkovLhGbp7VRhMhGThYt8WDwviXgaVAD8DjaVa5G5DApwC6tF") # Check that there's 100 UTXOs on each of the nodes assert_equal(len(self.nodes[0].listunspent()), 100) assert_equal(len(self.nodes[1].listunspent()), 100) assert_equal(len(self.nodes[2].listunspent()), 200) walletinfo = self.nodes[2].getbalance() assert_equal(walletinfo["CBT"], 21000000) assert_equal(walletinfo["ISSUANCE"], 500000) print("Mining blocks...") self.nodes[2].generate(101) self.sync_all() asscript = "76a914bc835aff853179fa88f2900f9003bb674e17ed4288ac"; genhash = self.nodes[2].getblockhash(0) genblock = self.nodes[2].getblock(genhash) for txid in genblock["tx"]: rawtx = self.nodes[2].getrawtransaction(txid,True) if "assetlabel" in rawtx["vout"][0]: if rawtx["vout"][0]["assetlabel"] == "ISSUANCE": asasset = rawtx["vout"][0]["asset"] astxid = txid asvalue = rawtx["vout"][0]["value"] assert_equal(self.nodes[0].getbalance("", 0, False, "CBT"), 21000000) assert_equal(self.nodes[1].getbalance("", 0, False, "CBT"), 21000000) assert_equal(self.nodes[2].getbalance("", 0, False, "CBT"), 21000000) #Set all OP_TRUE genesis outputs to single node self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 21000000, "", "", True) self.nodes[0].generate(101) self.sync_all() assert_equal(self.nodes[0].getbalance("", 0, False, "CBT"), 21000000) assert_equal(self.nodes[1].getbalance("", 0, False, "CBT"), 0) assert_equal(self.nodes[2].getbalance("", 0, False, "CBT"), 0) #self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1000000) #self.nodes[0].generate(1) #self.sync_all() #self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 100000) #self.nodes[0].generate(101) #self.sync_all() #assert_equal(self.nodes[0].getbalance(), 21000000-1100000) #assert_equal(self.nodes[1].getbalance(), 1000000) #assert_equal(self.nodes[2].getbalance(), 100000) # Send 21 BTC from 0 to 2 using sendtoaddress call. txid1 = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11) txout1v0 = self.nodes[0].gettxout(txid1, 0) rawtx1 = self.nodes[0].getrawtransaction(txid1, 1) #amountcommit1 = rawtx1["vout"][0]["amountcommitment"] assert_equal(txout1v0['confirmations'], 0) assert(not txout1v0['coinbase']) #assert_equal(amountcommit1, txout1v0['amountcommitment']) txid2 = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10) txout2v0 = self.nodes[0].gettxout(txid2, 0) rawtx2 = self.nodes[0].getrawtransaction(txid2, 1) #amountcommit2 = rawtx2["vout"][0]["amountcommitment"] assert_equal(txout2v0['confirmations'], 0) assert(not txout2v0['coinbase']) #assert_equal(amountcommit2, txout2v0['amountcommitment']) walletinfo = self.nodes[0].getwalletinfo("CBT") assert_equal(walletinfo['immature_balance'], 0) # Have node0 mine a block, thus it will collect its own fee. Confirm previous transactions. self.nodes[0].generate(1) self.sync_all() # Exercise locking of unspent outputs unspent_0 = self.nodes[2].listunspent(1, 9999999, [], True, "CBT")[0] unspent_0 = {"txid": unspent_0["txid"], "vout": unspent_0["vout"]} self.nodes[2].lockunspent(False, [unspent_0]) assert_raises_message(JSONRPCException, "Insufficient funds", self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20) assert_equal([unspent_0], self.nodes[2].listlockunspent()) self.nodes[2].lockunspent(True, [unspent_0]) assert_equal(len(self.nodes[2].listlockunspent()), 0) # Have node1 generate 100 blocks (so node0 can recover the fee) self.nodes[1].generate(100) self.sync_all() # node0 should end up with 100 btc in block rewards plus fees, but # minus the 21 plus fees sent to node2 assert_equal(self.nodes[0].getbalance("", 0, False, "CBT"), 21000000-21) assert_equal(self.nodes[2].getbalance("", 0, False, "CBT"), 21) # Node0 should have three spendable outputs since 0-value coinbase outputs will be OP_RETURN. # Create a couple of transactions to send them to node2, submit them through # node1, and make sure both node0 and node2 pick them up properly: node0utxos = self.nodes[0].listunspent(1, 9999999, [], True, "CBT") assert_equal(len(node0utxos), 3) # create both transactions txns_to_send = [] for utxo in node0utxos: if utxo["amount"] <= 3: # arbitrary value of 3? continue inputs = [] outputs = {} inputs.append({ "txid" : utxo["txid"], "vout" : utxo["vout"]}) outputs = {self.nodes[2].getnewaddress("from1"): utxo["amount"] - Decimal('1'), "fee": Decimal('1')} raw_tx = self.nodes[0].createrawtransaction(inputs, outputs) raw_tx = self.nodes[0].blindrawtransaction(raw_tx) txns_to_send.append(self.nodes[0].signrawtransaction(raw_tx)) # Have node 1 (miner) send the transaction txid = self.nodes[1].sendrawtransaction(txns_to_send[0]["hex"], True) # Have node1 mine a block to confirm transaction: self.nodes[1].generate(1) self.sync_all() #test creatation of raw multisig issuance transactions #get a new address and public and private key for each node address_node1 = self.nodes[0].getnewaddress() val_addr_node1 = self.nodes[0].validateaddress(address_node1) privkey_node1 = self.nodes[0].dumpprivkey(address_node1) address_node2 =self.nodes[1].getnewaddress() val_addr_node2 = self.nodes[1].validateaddress(address_node2) privkey_node2 =self.nodes[1].dumpprivkey(address_node2) address_node3 =self.nodes[2].getnewaddress() val_addr_node3 = self.nodes[2].validateaddress(address_node3) privkey_node3 =self.nodes[2].dumpprivkey(address_node3) #create 2 of 3 multisig P2SH script and address multisig = self.nodes[0].createmultisig(2,[val_addr_node1["pubkey"],val_addr_node2["pubkey"],val_addr_node3["pubkey"]]) #send some policy asset to the P2SH address pa_txid = self.nodes[2].sendtoaddress(multisig["address"],1,"","",False,asasset) self.nodes[1].generate(1) self.sync_all() #get the vout and scriptPubKey of the multisig output vout = 0 pa_tx = self.nodes[1].getrawtransaction(pa_txid,1) for val in pa_tx["vout"]: for i,j in val.items(): if i == "n": vout_t = j for i,j in val.items(): if i == "scriptPubKey": for i2,j2 in j.items(): if i2 == "hex": script_t = j2 for i2,j2 in j.items(): if(i2 == "type" and j2 == "scripthash"): script_pk = script_t vout = vout_t #get address to send tokens and re-issuance tokens asset_addr = self.nodes[1].getnewaddress() token_addr = self.nodes[1].getnewaddress() #create an unsigned raw issuance transaction issuance_tx = self.nodes[1].createrawissuance(asset_addr,10.0,token_addr,1.0,multisig["address"],1.0000,'1',pa_txid,str(vout)) #node1 partially sign transaction partial_signed = self.nodes[0].signrawtransaction(issuance_tx["rawtx"],[{"txid":pa_txid,"vout":vout,"scriptPubKey":script_pk,"redeemScript":multisig["redeemScript"]}],[privkey_node1]) assert(not partial_signed["complete"]) #node1 partially sign transaction signed_tx = self.nodes[1].signrawtransaction(partial_signed["hex"],[{"txid":pa_txid,"vout":vout,"scriptPubKey":script_pk,"redeemScript":multisig["redeemScript"]}],[privkey_node2]) assert(signed_tx["complete"]) self.nodes[1].generate(2) self.sync_all() #submit signed transaction to network submit = self.nodes[1].sendrawtransaction(signed_tx["hex"]) #confirm transaction accepted by mempool mempool_tx = self.nodes[1].getrawmempool() assert_equal(mempool_tx[0],submit) self.nodes[1].generate(10) self.sync_all() #confirm asset can be spent by node2 wallet asset_addr2 = self.nodes[0].getnewaddress() asset_tx = self.nodes[1].sendtoaddress(asset_addr2,5,' ',' ',False,issuance_tx["asset"],True) mempool1 = self.nodes[1].getrawmempool() assert_equal(mempool1[0],asset_tx) # Test address prefix values returned by getsidechaininfo rpc addr_prefixes = self.nodes[0].getsidechaininfo()["addr_prefixes"] for prefix in addr_prefixes: assert_greater_than_or_equal(int(addr_prefixes[prefix]), 0) assert_greater_than(255, int(addr_prefixes[prefix])) # Test address reconstruction using address prefixes # p2pkh address correctly formed addr = self.nodes[0].getnewaddress() pubkey = self.nodes[0].validateaddress(addr)['pubkey'] pubkey = hex_str_to_bytes(pubkey) assert_equal(addr,byte_to_base58(hash160(pubkey), addr_prefixes['PUBKEY_ADDRESS'])) # p2sh address isvalid? p2sh = byte_to_base58(hash160(CScript([OP_TRUE])), addr_prefixes['SCRIPT_ADDRESS']) assert(self.nodes[0].validateaddress(p2sh)['isvalid']) # priv key = generate new and test if import successful with SECRET_KEY prefix k = CECKey() k.set_compressed(True) pk_bytes = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).digest() pk_bytes = pk_bytes + b'\x01' k.set_secretbytes(pk_bytes) key = byte_to_base58(pk_bytes, addr_prefixes['SECRET_KEY']) assert_equal(self.nodes[0].importprivkey(key), None) # ensure import is successful # test blind prefix - construct expected createblindedaddress() return value and compare multisig_addr = self.nodes[2].createmultisig(2,["0222c31615e457119c2cb33821c150585c8b6a571a511d3cd07d27e7571e02c76e", "039bac374a8cd040ed137d0ce837708864e70012ad5766030aee1eb2f067b43d7f"])['address'] # blinding pubkey blinded_pubkey = self.nodes[2].validateaddress(self.nodes[2].getnewaddress())['pubkey'] blinded_addr = self.nodes[2].createblindedaddress(multisig_addr,blinded_pubkey) conf_addr_prefix = hex(addr_prefixes['BLINDED_ADDRESS'])[2:] if len(hex(addr_prefixes['BLINDED_ADDRESS'])[2:]) == 2 else '0' + str(hex(addr_prefixes['BLINDED_ADDRESS'])[2:]) secret_key_prefix = hex(addr_prefixes['SCRIPT_ADDRESS'])[2:] if len(hex(addr_prefixes['SCRIPT_ADDRESS'])[2:]) == 2 else '0' + str(hex(addr_prefixes['SCRIPT_ADDRESS'])[:2]) # construct expected createblindedaddress() return value expected_addr_bytes = \ str(conf_addr_prefix) + \ str(secret_key_prefix) + \ str(blinded_pubkey) + \ base58_to_bytes(multisig_addr)[2:] assert_equal(expected_addr_bytes,base58_to_bytes(blinded_addr)) ###################################################################### #################### END OF WORKING TESTS ########################### ###################################################################### return #TODO fix the rest txoutv0 = self.nodes[0].gettxout(txid, 0) assert_equal(txoutv0['confirmations'], 1) assert(not txoutv0['coinbase']) assert_equal(self.nodes[0].getbalance(), 0) assert_equal(self.nodes[2].getbalance(), 94) assert_equal(self.nodes[2].getbalance("from1"), 94-21) # Send 10 BTC normal address = self.nodes[0].getnewaddress("test") fee_per_byte = Decimal('0.001') / 1000 self.nodes[2].settxfee(fee_per_byte * 1000) txid = self.nodes[2].sendtoaddress(address, 10, "", "", False) self.nodes[2].generate(1) self.sync_all() node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), Decimal('84'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid))) assert_equal(self.nodes[0].getbalance(), Decimal('10')) # Send 10 BTC with subtract fee from amount txid = self.nodes[2].sendtoaddress(address, 10, "", "", True) self.nodes[2].generate(1) self.sync_all() node_2_bal -= Decimal('10') assert_equal(self.nodes[2].getbalance(), node_2_bal) node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid))) # Sendmany 10 BTC txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [], {'fee': 'CBT'}) self.nodes[2].generate(1) self.sync_all() node_0_bal += Decimal('10') node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid))) assert_equal(self.nodes[0].getbalance(), node_0_bal) # Sendmany 10 BTC with subtract fee from amount txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [address], {'fee': 'CBT'}) self.nodes[2].generate(1) self.sync_all() node_2_bal -= Decimal('10') assert_equal(self.nodes[2].getbalance(), node_2_bal) node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid))) # Test ResendWalletTransactions: # Create a couple of transactions, then start up a fourth # node (nodes[3]) and ask nodes[0] to rebroadcast. # EXPECT: nodes[3] should have those transactions in its mempool. txid1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1) txid2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1) sync_mempools(self.nodes) self.nodes.append(start_node(3, self.options.tmpdir, self.extra_args[3])) connect_nodes_bi(self.nodes, 0, 3) sync_blocks(self.nodes) relayed = self.nodes[0].resendwallettransactions() assert_equal(set(relayed), {txid1, txid2}) sync_mempools(self.nodes) assert(txid1 in self.nodes[3].getrawmempool()) # Exercise balance rpcs assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], 1) assert_equal(self.nodes[0].getunconfirmedbalance(), 1) #check if we can list zero value tx as available coins #1. create rawtx #2. hex-changed one output to 0.0 #3. sign and send #4. check if recipient (node0) can list the zero value tx usp = self.nodes[1].listunspent() inputs = [{"txid":usp[0]['txid'], "vout":usp[0]['vout']}] outputs = {self.nodes[1].getnewaddress(): 49.998, self.nodes[0].getnewaddress(): 11.11} rawTx = self.nodes[1].createrawtransaction(inputs, outputs).replace("c0833842", "00000000") #replace 11.11 with 0.0 (int32) decRawTx = self.nodes[1].decoderawtransaction(rawTx) signedRawTx = self.nodes[1].signrawtransaction(rawTx) decRawTx = self.nodes[1].decoderawtransaction(signedRawTx['hex']) zeroValueTxid= decRawTx['txid'] sendResp = self.nodes[1].sendrawtransaction(signedRawTx['hex']) self.sync_all() self.nodes[1].generate(1) #mine a block self.sync_all() unspentTxs = self.nodes[0].listunspent() #zero value tx must be in listunspents output found = False for uTx in unspentTxs: if uTx['txid'] == zeroValueTxid: found = True assert_equal(uTx['amount'], Decimal('0')) assert(found) #do some -walletbroadcast tests stop_nodes(self.nodes) self.nodes = start_nodes(3, self.options.tmpdir, [["-walletbroadcast=0"],["-walletbroadcast=0"],["-walletbroadcast=0"]]) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,0,2) self.sync_all() txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2) txObjNotBroadcasted = self.nodes[0].gettransaction(txIdNotBroadcasted) self.nodes[1].generate(1) #mine a block, tx should not be in there self.sync_all() assert_equal(self.nodes[2].getbalance(), node_2_bal) #should not be changed because tx was not broadcasted #now broadcast from another node, mine a block, sync, and check the balance self.nodes[1].sendrawtransaction(txObjNotBroadcasted['hex']) self.nodes[1].generate(1) self.sync_all() node_2_bal += 2 txObjNotBroadcasted = self.nodes[0].gettransaction(txIdNotBroadcasted) assert_equal(self.nodes[2].getbalance(), node_2_bal) #create another tx txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2) #restart the nodes with -walletbroadcast=1 stop_nodes(self.nodes) self.nodes = start_nodes(3, self.options.tmpdir) connect_nodes_bi(self.nodes,0,1) connect_nodes_bi(self.nodes,1,2) connect_nodes_bi(self.nodes,0,2) sync_blocks(self.nodes) self.nodes[0].generate(1) sync_blocks(self.nodes) node_2_bal += 2 #tx should be added to balance because after restarting the nodes tx should be broadcastet assert_equal(self.nodes[2].getbalance(), node_2_bal) #send a tx with value in a string (PR#6380 +) txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "2") txObj = self.nodes[0].gettransaction(txId) assert_equal(txObj['amount'], Decimal('-2')) txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "0.0001") txObj = self.nodes[0].gettransaction(txId) assert_equal(txObj['amount'], Decimal('-0.0001')) #check if JSON parser can handle scientific notation in strings txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1e-4") txObj = self.nodes[0].gettransaction(txId) assert_equal(txObj['amount'], Decimal('-0.0001')) try: txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1f-4") except JSONRPCException as e: assert("Invalid amount" in e.error['message']) else: raise AssertionError("Must not parse invalid amounts") try: self.nodes[0].generate("2") raise AssertionError("Must not accept strings as numeric") except JSONRPCException as e: assert("not an integer" in e.error['message']) # Import address and private key to check correct behavior of spendable unspents # 1. Send some coins to generate new UTXO address_to_import = self.nodes[2].getnewaddress() txid = self.nodes[0].sendtoaddress(address_to_import, 1) self.nodes[0].generate(1) self.sync_all() # 2. Import address from node2 to node1 self.nodes[1].importaddress(address_to_import) # 3. Validate that the imported address is watch-only on node1 assert(self.nodes[1].validateaddress(address_to_import)["iswatchonly"]) # 4. Check that the unspents after import are not spendable assert_array_result(self.nodes[1].listunspent(), {"address": address_to_import}, {"spendable": False}) # 5. Import private key of the previously imported address on node1 priv_key = self.nodes[2].dumpprivkey(address_to_import) self.nodes[1].importprivkey(priv_key) # 6. Check that the unspents are now spendable on node1 assert_array_result(self.nodes[1].listunspent(), {"address": address_to_import}, {"spendable": True}) # Mine a block from node0 to an address from node1 cbAddr = self.nodes[1].getnewaddress() blkHash = self.nodes[0].generatetoaddress(1, cbAddr)[0] cbTxId = self.nodes[0].getblock(blkHash)['tx'][0] self.sync_all() # Check that the txid and balance is found by node1 self.nodes[1].gettransaction(cbTxId) # check if wallet or blockchain maintenance changes the balance self.sync_all() blocks = self.nodes[0].generate(2) self.sync_all() balance_nodes = [self.nodes[i].getbalance() for i in range(3)] block_count = self.nodes[0].getblockcount() # Check modes: # - True: unicode escaped as \u.... # - False: unicode directly as UTF-8 for mode in [True, False]: self.nodes[0].ensure_ascii = mode # unicode check: Basic Multilingual Plane, Supplementary Plane respectively for s in [u'ббаБаА', u'№ Ё']: addr = self.nodes[0].getaccountaddress(s) label = self.nodes[0].getaccount(addr) assert_equal(label, s) assert(s in self.nodes[0].listaccounts().keys()) self.nodes[0].ensure_ascii = True # restore to default # maintenance tests maintenance = [ '-rescan', '-reindex', '-zapwallettxes=1', '-zapwallettxes=2', # disabled until issue is fixed: https://github.com/bitcoin/bitcoin/issues/7463 # '-salvagewallet', ] chainlimit = 6 for m in maintenance: print("check " + m) stop_nodes(self.nodes) # set lower ancestor limit for later self.nodes = start_nodes(3, self.options.tmpdir, [[m, "-limitancestorcount="+str(chainlimit)]] * 3) while m == '-reindex' and [block_count] * 3 != [self.nodes[i].getblockcount() for i in range(3)]: # reindex will leave rpc warm up "early"; Wait for it to finish time.sleep(0.1) assert_equal(balance_nodes, [self.nodes[i].getbalance() for i in range(3)]) # Exercise listsinceblock with the last two blocks coinbase_tx_1 = self.nodes[0].listsinceblock(blocks[0]) assert_equal(coinbase_tx_1["lastblock"], blocks[1]) assert_equal(len(coinbase_tx_1["transactions"]), 1) assert_equal(coinbase_tx_1["transactions"][0]["blockhash"], blocks[1]) assert_equal(len(self.nodes[0].listsinceblock(blocks[1])["transactions"]), 0) # ==Check that wallet prefers to use coins that don't exceed mempool limits ===== # Get all non-zero utxos together chain_addrs = [self.nodes[0].getnewaddress(), self.nodes[0].getnewaddress()] singletxid = self.nodes[0].sendtoaddress(chain_addrs[0], self.nodes[0].getbalance(), "", "", True) self.nodes[0].generate(1) node0_balance = self.nodes[0].getbalance() # Split into two chains rawtx = self.nodes[0].createrawtransaction([{"txid":singletxid, "vout":0}], {chain_addrs[0]:node0_balance/2-Decimal('0.01'), chain_addrs[1]:node0_balance/2-Decimal('0.01')}) signedtx = self.nodes[0].signrawtransaction(rawtx) singletxid = self.nodes[0].sendrawtransaction(signedtx["hex"]) self.nodes[0].generate(1) # Make a long chain of unconfirmed payments without hitting mempool limit # Each tx we make leaves only one output of change on a chain 1 longer # Since the amount to send is always much less than the outputs, we only ever need one output # So we should be able to generate exactly chainlimit txs for each original output sending_addr = self.nodes[1].getnewaddress() txid_list = [] for i in range(chainlimit*2): txid_list.append(self.nodes[0].sendtoaddress(sending_addr, Decimal('0.0001'))) assert_equal(self.nodes[0].getmempoolinfo()['size'], chainlimit*2) assert_equal(len(txid_list), chainlimit*2) # Without walletrejectlongchains, we will still generate a txid # The tx will be stored in the wallet but not accepted to the mempool extra_txid = self.nodes[0].sendtoaddress(sending_addr, Decimal('0.0001')) assert(extra_txid not in self.nodes[0].getrawmempool()) assert(extra_txid in [tx["txid"] for tx in self.nodes[0].listtransactions()]) self.nodes[0].abandontransaction(extra_txid) total_txs = len(self.nodes[0].listtransactions("*",99999)) # Try with walletrejectlongchains # Double chain limit but require combining inputs, so we pass SelectCoinsMinConf stop_node(self.nodes[0],0) self.nodes[0] = start_node(0, self.options.tmpdir, ["-walletrejectlongchains", "-limitancestorcount="+str(2*chainlimit)]) # wait for loadmempool timeout = 10 while (timeout > 0 and len(self.nodes[0].getrawmempool()) < chainlimit*2): time.sleep(0.5) timeout -= 0.5 assert_equal(len(self.nodes[0].getrawmempool()), chainlimit*2) node0_balance = self.nodes[0].getbalance() # With walletrejectlongchains we will not create the tx and store it in our wallet. assert_raises_message(JSONRPCException, "mempool chain", self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01')) # Verify nothing new in wallet assert_equal(total_txs, len(self.nodes[0].listtransactions("*",99999)))
def run_test(self): node = self.nodes[0] # convenience reference to the node self.bootstrap_p2p() # Add one p2p connection to the node best_block = self.nodes[0].getbestblockhash() tip = int(best_block, 16) best_block_time = self.nodes[0].getblock(best_block)['time'] block_time = best_block_time + 1 privkey = b"aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7" key = CECKey() key.set_secretbytes(privkey) key.set_compressed(True) pubkey = CPubKey(key.get_pubkey()) pubkeyhash = hash160(pubkey) SCRIPT_PUB_KEY = CScript([ CScriptOp(OP_DUP), CScriptOp(OP_HASH160), pubkeyhash, CScriptOp(OP_EQUALVERIFY), CScriptOp(OP_CHECKSIG) ]) self.log.info("Create a new block with an anyone-can-spend coinbase.") height = 1 block = create_block(tip, create_coinbase(height, pubkey), block_time) block.solve(self.signblockprivkey) # Save the coinbase for later block1 = block tip = block.sha256 node.p2p.send_blocks_and_test([block], node, success=True) # b'\x64' is OP_NOTIF # Transaction will be rejected with code 16 (REJECT_INVALID) self.log.info('Test a transaction that is rejected') tx1 = create_tx_with_script(block1.vtx[0], 0, script_sig=b'\x64' * 35, amount=50 * COIN - 12000) node.p2p.send_txs_and_test([tx1], node, success=False, expect_disconnect=False) # Make two p2p connections to provide the node with orphans # * p2ps[0] will send valid orphan txs (one with low fee) # * p2ps[1] will send an invalid orphan tx (and is later disconnected for that) self.reconnect_p2p(num_connections=2) self.log.info('Test orphan transaction handling ... ') # Create a root transaction that we withhold until all dependend transactions # are sent out and in the orphan cache tx_withhold = CTransaction() tx_withhold.vin.append( CTxIn(outpoint=COutPoint(block1.vtx[0].malfixsha256, 0))) tx_withhold.vout.append( CTxOut(nValue=50 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY)) tx_withhold.calc_sha256() (sighash, err) = SignatureHash(CScript([pubkey, OP_CHECKSIG]), tx_withhold, 0, SIGHASH_ALL) signature = key.sign(sighash) + b'\x01' # 0x1 is SIGHASH_ALL tx_withhold.vin[0].scriptSig = CScript([signature]) # Our first orphan tx with some outputs to create further orphan txs tx_orphan_1 = CTransaction() tx_orphan_1.vin.append( CTxIn(outpoint=COutPoint(tx_withhold.malfixsha256, 0))) tx_orphan_1.vout = [ CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY) ] * 3 tx_orphan_1.calc_sha256() (sighash, err) = SignatureHash(SCRIPT_PUB_KEY, tx_orphan_1, 0, SIGHASH_ALL) signature = key.sign(sighash) + b'\x01' # 0x1 is SIGHASH_ALL tx_orphan_1.vin[0].scriptSig = CScript([signature, pubkey]) # A valid transaction with low fee tx_orphan_2_no_fee = CTransaction() tx_orphan_2_no_fee.vin.append( CTxIn(outpoint=COutPoint(tx_orphan_1.malfixsha256, 0))) tx_orphan_2_no_fee.vout.append( CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY)) (sighash, err) = SignatureHash(SCRIPT_PUB_KEY, tx_orphan_2_no_fee, 0, SIGHASH_ALL) signature = key.sign(sighash) + b'\x01' # 0x1 is SIGHASH_ALL tx_orphan_2_no_fee.vin[0].scriptSig = CScript([signature, pubkey]) # A valid transaction with sufficient fee tx_orphan_2_valid = CTransaction() tx_orphan_2_valid.vin.append( CTxIn(outpoint=COutPoint(tx_orphan_1.malfixsha256, 1))) tx_orphan_2_valid.vout.append( CTxOut(nValue=10 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY)) tx_orphan_2_valid.calc_sha256() (sighash, err) = SignatureHash(SCRIPT_PUB_KEY, tx_orphan_2_valid, 0, SIGHASH_ALL) signature = key.sign(sighash) + b'\x01' # 0x1 is SIGHASH_ALL tx_orphan_2_valid.vin[0].scriptSig = CScript([signature, pubkey]) # An invalid transaction with negative fee tx_orphan_2_invalid = CTransaction() tx_orphan_2_invalid.vin.append( CTxIn(outpoint=COutPoint(tx_orphan_1.malfixsha256, 2))) tx_orphan_2_invalid.vout.append( CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY)) (sighash, err) = SignatureHash(SCRIPT_PUB_KEY, tx_orphan_2_invalid, 0, SIGHASH_ALL) signature = key.sign(sighash) + b'\x01' # 0x1 is SIGHASH_ALL tx_orphan_2_invalid.vin[0].scriptSig = CScript([signature, pubkey]) self.log.info('Send the orphans ... ') # Send valid orphan txs from p2ps[0] node.p2p.send_txs_and_test( [tx_orphan_1, tx_orphan_2_no_fee, tx_orphan_2_valid], node, success=False) # Send invalid tx from p2ps[1] node.p2ps[1].send_txs_and_test([tx_orphan_2_invalid], node, success=False) assert_equal(0, node.getmempoolinfo()['size']) # Mempool should be empty assert_equal(2, len(node.getpeerinfo())) # p2ps[1] is still connected self.log.info('Send the withhold tx ... ') node.p2p.send_txs_and_test([tx_withhold], node, success=True) # Transactions that should end up in the mempool expected_mempool = { t.hashMalFix for t in [ tx_withhold, # The transaction that is the root for all orphans tx_orphan_1, # The orphan transaction that splits the coins tx_orphan_2_valid, # The valid transaction (with sufficient fee) ] } # Transactions that do not end up in the mempool # tx_orphan_no_fee, because it has too low fee (p2ps[0] is not disconnected for relaying that tx) # tx_orphan_invaid, because it has negative fee (p2ps[1] is disconnected for relaying that tx) wait_until(lambda: 1 == len(node.getpeerinfo()), timeout=12) # p2ps[1] is no longer connected assert_equal(expected_mempool, set(node.getrawmempool())) # restart node with sending BIP61 messages disabled, check that it disconnects without sending the reject message self.log.info( 'Test a transaction that is rejected, with BIP61 disabled') self.restart_node(0, ['-enablebip61=0', '-persistmempool=0']) self.reconnect_p2p(num_connections=1) node.p2p.send_txs_and_test([tx1], node, success=False, expect_disconnect=False) # send_txs_and_test will have waited for disconnect, so we can safely check that no reject has been received assert_equal(node.p2p.reject_code_received, None)