def make_utxo(node, amount, confirmed=True, scriptPubKey=CScript([1])): """Create a txout with a given amount and scriptPubKey Mines coins as needed. confirmed - txouts created will be confirmed in the blockchain; unconfirmed otherwise. """ fee = 1*COIN while node.getbalance()['bitcoin'] < satoshi_round((amount + fee)/COIN): node.generate(100) new_addr = node.getnewaddress() unblinded_addr = node.validateaddress(new_addr)["unconfidential"] txidstr = node.sendtoaddress(new_addr, satoshi_round((amount+fee)/COIN)) tx1 = node.getrawtransaction(txidstr, 1) txid = int(txidstr, 16) i = None for i, txout in enumerate(tx1['vout']): if txout['scriptPubKey']['type'] == "fee": continue # skip fee outputs if txout['scriptPubKey']['addresses'] == [unblinded_addr]: break assert i is not None tx2 = CTransaction() tx2.vin = [CTxIn(COutPoint(txid, i))] tx1raw = CTransaction() tx1raw.deserialize(BytesIO(hex_str_to_bytes(node.getrawtransaction(txidstr)))) feeout = CTxOut(CTxOutValue(tx1raw.vout[i].nValue.getAmount() - amount)) tx2.vout = [CTxOut(amount, scriptPubKey), feeout] tx2.rehash() signed_tx = node.signrawtransactionwithwallet(txToHex(tx2)) txid = node.sendrawtransaction(signed_tx['hex'], True) # If requested, ensure txouts are confirmed. if confirmed: mempool_size = len(node.getrawmempool()) while mempool_size > 0: node.generate(1) new_size = len(node.getrawmempool()) # Error out if we have something stuck in the mempool, as this # would likely be a bug. assert(new_size < mempool_size) mempool_size = new_size return COutPoint(int(txid, 16), 0)
def split_inputs(from_node, txins, txouts, initial_split=False): """Generate a lot of inputs so we can generate a ton of transactions. This function takes an input from txins, and creates and sends a transaction which splits the value into 2 outputs which are appended to txouts. Previously this was designed to be small inputs so they wouldn't have a high coin age when the notion of priority still existed.""" prevtxout = txins.pop() tx = CTransaction() tx.vin.append(CTxIn(COutPoint(int(prevtxout["txid"], 16), prevtxout["vout"]), b"")) half_change = satoshi_round(prevtxout["amount"] / 2) rem_change = prevtxout["amount"] - half_change - Decimal("0.00001000") tx.vout.append(CTxOut(int(half_change * COIN), P2SH_1)) tx.vout.append(CTxOut(int(rem_change * COIN), P2SH_2)) # If this is the initial split we actually need to sign the transaction # Otherwise we just need to insert the proper ScriptSig if (initial_split): completetx = from_node.signrawtransactionwithwallet(ToHex(tx))["hex"] else: tx.vin[0].scriptSig = SCRIPT_SIG[prevtxout["vout"]] completetx = ToHex(tx) txid = from_node.sendrawtransaction(completetx, True) txouts.append({"txid": txid, "vout": 0, "amount": half_change}) txouts.append({"txid": txid, "vout": 1, "amount": rem_change})
def make_utxo(node, amount, confirmed=True, scriptPubKey=CScript([1])): """Create a txout with a given amount and scriptPubKey Mines coins as needed. confirmed - txouts created will be confirmed in the blockchain; unconfirmed otherwise. """ fee = 1*COIN while node.getbalance() < satoshi_round((amount + fee)/COIN): node.generate(100) new_addr = node.getnewaddress() txid = node.sendtoaddress(new_addr, satoshi_round((amount+fee)/COIN)) tx1 = node.getrawtransaction(txid, 1) txid = int(txid, 16) i = None for i, txout in enumerate(tx1['vout']): if txout['scriptPubKey']['addresses'] == [new_addr]: break assert i is not None tx2 = CTransaction() tx2.vin = [CTxIn(COutPoint(txid, i))] tx2.vout = [CTxOut(amount, scriptPubKey)] tx2.rehash() signed_tx = node.signrawtransactionwithwallet(txToHex(tx2)) txid = node.sendrawtransaction(signed_tx['hex'], True) # If requested, ensure txouts are confirmed. if confirmed: mempool_size = len(node.getrawmempool()) while mempool_size > 0: node.generate(1) new_size = len(node.getrawmempool()) # Error out if we have something stuck in the mempool, as this # would likely be a bug. assert(new_size < mempool_size) mempool_size = new_size return COutPoint(int(txid, 16), 0)
def create_null_data_tx(self, data_size): node = self.nodes[0] utxos = node.listunspent() assert(len(utxos) > 0) utxo = utxos[0] tx = CTransaction() value = int(satoshi_round(utxo["amount"] - self.relayfee) * COIN) tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]))] script = CScript([OP_RETURN, b'x' * data_size]) tx.vout = [CTxOut(value, script)] tx_signed = node.signrawtransaction(ToHex(tx))["hex"] return tx_signed
def chain_transaction(self, node, parent_txid, vout, value, fee, num_outputs): send_value = satoshi_round((value - fee)/num_outputs) inputs = [ {'txid' : parent_txid, 'vout' : vout} ] outputs = {} for i in range(num_outputs): outputs[node.getnewaddress()] = send_value rawtx = node.createrawtransaction(inputs, outputs) signedtx = node.signrawtransactionwithwallet(rawtx) txid = node.sendrawtransaction(signedtx['hex']) fulltx = node.getrawtransaction(txid, 1) assert(len(fulltx['vout']) == num_outputs) # make sure we didn't generate a change output return (txid, send_value)
def create_and_tx(self, count): node = self.nodes[0] utxos = node.listunspent() assert(len(utxos) > 0) utxo = utxos[0] tx = CTransaction() value = int(satoshi_round( utxo["amount"] - self.relayfee) * COIN) // count tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]))] tx.vout = [] for _ in range(count): tx.vout.append(CTxOut(value, CScript([OP_1, OP_1, OP_AND]))) tx_signed = node.signrawtransaction(ToHex(tx))["hex"] return tx_signed
def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee_increment): """Create and send a transaction with a random fee. The transaction pays to a trivial P2SH script, and assumes that its inputs are of the same form. The function takes a list of confirmed outputs and unconfirmed outputs and attempts to use the confirmed list first for its inputs. It adds the newly created outputs to the unconfirmed list. Returns (raw transaction, fee).""" # It's best to exponentially distribute our random fees # because the buckets are exponentially spaced. # Exponentially distributed from 1-128 * fee_increment rand_fee = float(fee_increment) * (1.1892 ** random.randint(0, 28)) # Total fee ranges from min_fee to min_fee + 127*fee_increment fee = min_fee - fee_increment + satoshi_round(rand_fee) tx = CTransaction() total_in = Decimal("0.00000000") while total_in <= (amount + fee) and len(conflist) > 0: t = conflist.pop(0) total_in += t["amount"] tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), b"")) if total_in <= amount + fee: while total_in <= (amount + fee) and len(unconflist) > 0: t = unconflist.pop(0) total_in += t["amount"] tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), b"")) if total_in <= amount + fee: raise RuntimeError("Insufficient funds: need %d, have %d" % (amount + fee, total_in)) tx.vout.append(CTxOut(int((total_in - amount - fee) * COIN), P2SH_1)) tx.vout.append(CTxOut(int(amount * COIN), P2SH_2)) tx.vout.append(CTxOut(int(fee*COIN))) # fee # These transactions don't need to be signed, but we still have to insert # the ScriptSig that will satisfy the ScriptPubKey. for inp in tx.vin: inp.scriptSig = SCRIPT_SIG[inp.prevout.n] txid = from_node.sendrawtransaction(ToHex(tx), True) unconflist.append({"txid": txid, "vout": 0, "amount": total_in - amount - fee}) unconflist.append({"txid": txid, "vout": 1, "amount": amount}) return (ToHex(tx), fee)
def test_disable_flag(self): # Create some unconfirmed inputs new_addr = self.nodes[0].getnewaddress() self.nodes[0].sendtoaddress(new_addr, 2) # send 2 BTC utxos = self.nodes[0].listunspent(0, 0) assert len(utxos) > 0 utxo = utxos[0] tx1 = CTransaction() value = int(satoshi_round(utxo["amount"] - self.relayfee)*COIN) # Check that the disable flag disables relative locktime. # If sequence locks were used, this would require 1 block for the # input to mature. sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 1 tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)] tx1.vout = [CTxOut(value, CScript([b'a']))] tx1_signed = self.nodes[0].signrawtransactionwithwallet(ToHex(tx1))["hex"] tx1_id = self.nodes[0].sendrawtransaction(tx1_signed) tx1_id = int(tx1_id, 16) # This transaction will enable sequence-locks, so this transaction should # fail tx2 = CTransaction() tx2.nVersion = 2 sequence_value = sequence_value & 0x7fffffff tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)] tx2.vout = [CTxOut(int(value - self.relayfee * COIN), CScript([b'a' * 35]))] tx2.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, ToHex(tx2)) # Setting the version back down to 1 should disable the sequence lock, # so this should be accepted. tx2.nVersion = 1 self.nodes[0].sendrawtransaction(ToHex(tx2))
def test_compactblock_construction(self, node, test_node, version, use_witness_address): # Generate a bunch of transactions. node.generate(101) num_transactions = 25 address = node.getnewaddress() if use_witness_address: # Want at least one segwit spend, so move all funds to # a witness address. address = node.getnewaddress(address_type='bech32') value_to_send = node.getbalance() node.sendtoaddress(address, satoshi_round(value_to_send - Decimal(0.1))) node.generate(1) segwit_tx_generated = False for i in range(num_transactions): txid = node.sendtoaddress(address, 0.1) hex_tx = node.gettransaction(txid)["hex"] tx = FromHex(CTransaction(), hex_tx) if not tx.wit.is_null(): segwit_tx_generated = True if use_witness_address: assert segwit_tx_generated # check that our test is not broken # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) test_node.wait_for_block_announcement(tip) # Make sure we will receive a fast-announce compact block self.request_cb_announcements(test_node, node, version) # Now mine a block, and look at the resulting compact block. test_node.clear_block_announcement() block_hash = int(node.generate(1)[0], 16) # Store the raw block in our internal format. block = FromHex(CBlock(), node.getblock("%064x" % block_hash, False)) for tx in block.vtx: tx.calc_sha256() block.rehash() # Wait until the block was announced (via compact blocks) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert ("cmpctblock" in test_node.last_message) # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( version, header_and_shortids, block_hash, block) # Now fetch the compact block using a normal non-announce getdata with mininode_lock: test_node.clear_block_announcement() inv = CInv(4, block_hash) # 4 == "CompactBlock" test_node.send_message(msg_getdata([inv])) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert ("cmpctblock" in test_node.last_message) # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( version, header_and_shortids, block_hash, block)
def run_test(self): # Mine some blocks and have them mature. peer_inv_store = self.nodes[0].add_p2p_connection( P2PTxInvStore()) # keep track of invs self.nodes[0].generate(COINBASE_MATURITY + 1) utxo = self.nodes[0].listunspent(10) txid = utxo[0]['txid'] vout = utxo[0]['vout'] value = utxo[0]['amount'] fee = Decimal("0.0001") # MAX_ANCESTORS transactions off a confirmed tx should be fine chain = [] witness_chain = [] for _ in range(MAX_ANCESTORS): (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1) value = sent_value chain.append(txid) # We need the wtxids to check P2P announcements fulltx = self.nodes[0].getrawtransaction(txid) witnesstx = self.nodes[0].decoderawtransaction(fulltx, True) witness_chain.append(witnesstx['hash']) # Wait until mempool transactions have passed initial broadcast (sent inv and received getdata) # Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between peer_inv_store.wait_for_broadcast(witness_chain) # Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor # count and fees should look correct mempool = self.nodes[0].getrawmempool(True) assert_equal(len(mempool), MAX_ANCESTORS) descendant_count = 1 descendant_fees = 0 descendant_vsize = 0 ancestor_vsize = sum([mempool[tx]['vsize'] for tx in mempool]) ancestor_count = MAX_ANCESTORS ancestor_fees = sum([mempool[tx]['fee'] for tx in mempool]) descendants = [] ancestors = list(chain) for x in reversed(chain): # Check that getmempoolentry is consistent with getrawmempool entry = self.nodes[0].getmempoolentry(x) assert_equal(entry, mempool[x]) # Check that the descendant calculations are correct assert_equal(mempool[x]['descendantcount'], descendant_count) descendant_fees += mempool[x]['fee'] assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']) assert_equal(mempool[x]['fees']['base'], mempool[x]['fee']) assert_equal(mempool[x]['fees']['modified'], mempool[x]['modifiedfee']) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN) assert_equal(mempool[x]['fees']['descendant'], descendant_fees) descendant_vsize += mempool[x]['vsize'] assert_equal(mempool[x]['descendantsize'], descendant_vsize) descendant_count += 1 # Check that ancestor calculations are correct assert_equal(mempool[x]['ancestorcount'], ancestor_count) assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN) assert_equal(mempool[x]['ancestorsize'], ancestor_vsize) ancestor_vsize -= mempool[x]['vsize'] ancestor_fees -= mempool[x]['fee'] ancestor_count -= 1 # Check that parent/child list is correct assert_equal(mempool[x]['spentby'], descendants[-1:]) assert_equal(mempool[x]['depends'], ancestors[-2:-1]) # Check that getmempooldescendants is correct assert_equal(sorted(descendants), sorted(self.nodes[0].getmempooldescendants(x))) # Check getmempooldescendants verbose output is correct for descendant, dinfo in self.nodes[0].getmempooldescendants( x, True).items(): assert_equal(dinfo['depends'], [chain[chain.index(descendant) - 1]]) if dinfo['descendantcount'] > 1: assert_equal(dinfo['spentby'], [chain[chain.index(descendant) + 1]]) else: assert_equal(dinfo['spentby'], []) descendants.append(x) # Check that getmempoolancestors is correct ancestors.remove(x) assert_equal(sorted(ancestors), sorted(self.nodes[0].getmempoolancestors(x))) # Check that getmempoolancestors verbose output is correct for ancestor, ainfo in self.nodes[0].getmempoolancestors( x, True).items(): assert_equal(ainfo['spentby'], [chain[chain.index(ancestor) + 1]]) if ainfo['ancestorcount'] > 1: assert_equal(ainfo['depends'], [chain[chain.index(ancestor) - 1]]) else: assert_equal(ainfo['depends'], []) # Check that getmempoolancestors/getmempooldescendants correctly handle verbose=true v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True) assert_equal(len(v_ancestors), len(chain) - 1) for x in v_ancestors.keys(): assert_equal(mempool[x], v_ancestors[x]) assert chain[-1] not in v_ancestors.keys() v_descendants = self.nodes[0].getmempooldescendants(chain[0], True) assert_equal(len(v_descendants), len(chain) - 1) for x in v_descendants.keys(): assert_equal(mempool[x], v_descendants[x]) assert chain[0] not in v_descendants.keys() # Check that ancestor modified fees includes fee deltas from # prioritisetransaction self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=1000) mempool = self.nodes[0].getrawmempool(True) ancestor_fees = 0 for x in chain: ancestor_fees += mempool[x]['fee'] assert_equal(mempool[x]['fees']['ancestor'], ancestor_fees + Decimal('0.00001')) assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN + 1000) # Undo the prioritisetransaction for later tests self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=-1000) # Check that descendant modified fees includes fee deltas from # prioritisetransaction self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=1000) mempool = self.nodes[0].getrawmempool(True) descendant_fees = 0 for x in reversed(chain): descendant_fees += mempool[x]['fee'] assert_equal(mempool[x]['fees']['descendant'], descendant_fees + Decimal('0.00001')) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 1000) # Adding one more transaction on to the chain should fail. assert_raises_rpc_error(-26, "too-long-mempool-chain", chain_transaction, self.nodes[0], [txid], [vout], value, fee, 1) # Check that prioritising a tx before it's added to the mempool works # First clear the mempool by mining a block. self.nodes[0].generate(1) self.sync_blocks() assert_equal(len(self.nodes[0].getrawmempool()), 0) # Prioritise a transaction that has been mined, then add it back to the # mempool by using invalidateblock. self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=2000) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # Keep node1's tip synced with node0 self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash()) # Now check that the transaction is in the mempool, with the right modified fee mempool = self.nodes[0].getrawmempool(True) descendant_fees = 0 for x in reversed(chain): descendant_fees += mempool[x]['fee'] if (x == chain[-1]): assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee'] + satoshi_round(0.00002)) assert_equal(mempool[x]['fees']['modified'], mempool[x]['fee'] + satoshi_round(0.00002)) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 2000) assert_equal(mempool[x]['fees']['descendant'], descendant_fees + satoshi_round(0.00002)) # Check that node1's mempool is as expected (-> custom ancestor limit) mempool0 = self.nodes[0].getrawmempool(False) mempool1 = self.nodes[1].getrawmempool(False) assert_equal(len(mempool1), MAX_ANCESTORS_CUSTOM) assert set(mempool1).issubset(set(mempool0)) for tx in chain[:MAX_ANCESTORS_CUSTOM]: assert tx in mempool1 # TODO: more detailed check of node1's mempool (fees etc.) # check transaction unbroadcast info (should be false if in both mempools) mempool = self.nodes[0].getrawmempool(True) for tx in mempool: assert_equal(mempool[tx]['unbroadcast'], False) # TODO: test ancestor size limits # Now test descendant chain limits txid = utxo[1]['txid'] value = utxo[1]['amount'] vout = utxo[1]['vout'] transaction_package = [] tx_children = [] # First create one parent tx with 10 children (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 10) parent_transaction = txid for i in range(10): transaction_package.append({ 'txid': txid, 'vout': i, 'amount': sent_value }) # Sign and send up to MAX_DESCENDANT transactions chained off the parent tx chain = [ ] # save sent txs for the purpose of checking node1's mempool later (see below) for _ in range(MAX_DESCENDANTS - 1): utxo = transaction_package.pop(0) (txid, sent_value) = chain_transaction(self.nodes[0], [utxo['txid']], [utxo['vout']], utxo['amount'], fee, 10) chain.append(txid) if utxo['txid'] is parent_transaction: tx_children.append(txid) for j in range(10): transaction_package.append({ 'txid': txid, 'vout': j, 'amount': sent_value }) mempool = self.nodes[0].getrawmempool(True) assert_equal(mempool[parent_transaction]['descendantcount'], MAX_DESCENDANTS) assert_equal(sorted(mempool[parent_transaction]['spentby']), sorted(tx_children)) for child in tx_children: assert_equal(mempool[child]['depends'], [parent_transaction]) # Sending one more chained transaction will fail utxo = transaction_package.pop(0) assert_raises_rpc_error(-26, "too-long-mempool-chain", chain_transaction, self.nodes[0], [utxo['txid']], [utxo['vout']], utxo['amount'], fee, 10) # Check that node1's mempool is as expected, containing: # - txs from previous ancestor test (-> custom ancestor limit) # - parent tx for descendant test # - txs chained off parent tx (-> custom descendant limit) self.wait_until(lambda: len(self.nodes[1].getrawmempool(False)) == MAX_ANCESTORS_CUSTOM + 1 + MAX_DESCENDANTS_CUSTOM, timeout=10) mempool0 = self.nodes[0].getrawmempool(False) mempool1 = self.nodes[1].getrawmempool(False) assert set(mempool1).issubset(set(mempool0)) assert parent_transaction in mempool1 for tx in chain[:MAX_DESCENDANTS_CUSTOM]: assert tx in mempool1 for tx in chain[MAX_DESCENDANTS_CUSTOM:]: assert tx not in mempool1 # TODO: more detailed check of node1's mempool (fees etc.) # TODO: test descendant size limits # Test reorg handling # First, the basics: self.nodes[0].generate(1) self.sync_blocks() self.nodes[1].invalidateblock(self.nodes[0].getbestblockhash()) self.nodes[1].reconsiderblock(self.nodes[0].getbestblockhash()) # Now test the case where node1 has a transaction T in its mempool that # depends on transactions A and B which are in a mined block, and the # block containing A and B is disconnected, AND B is not accepted back # into node1's mempool because its ancestor count is too high. # Create 8 transactions, like so: # Tx0 -> Tx1 (vout0) # \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7 # # Mine them in the next block, then generate a new tx8 that spends # Tx1 and Tx7, and add to node1's mempool, then disconnect the # last block. # Create tx0 with 2 outputs utxo = self.nodes[0].listunspent() txid = utxo[0]['txid'] value = utxo[0]['amount'] vout = utxo[0]['vout'] send_value = satoshi_round((value - fee) / 2) inputs = [{'txid': txid, 'vout': vout}] outputs = {} for _ in range(2): outputs[self.nodes[0].getnewaddress()] = send_value rawtx = self.nodes[0].createrawtransaction(inputs, outputs) signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) txid = self.nodes[0].sendrawtransaction(signedtx['hex']) tx0_id = txid value = send_value # Create tx1 tx1_id, _ = chain_transaction(self.nodes[0], [tx0_id], [0], value, fee, 1) # Create tx2-7 vout = 1 txid = tx0_id for _ in range(6): (txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 1) vout = 0 value = sent_value # Mine these in a block self.nodes[0].generate(1) self.sync_all() # Now generate tx8, with a big fee inputs = [{'txid': tx1_id, 'vout': 0}, {'txid': txid, 'vout': 0}] outputs = {self.nodes[0].getnewaddress(): send_value + value - 4 * fee} rawtx = self.nodes[0].createrawtransaction(inputs, outputs) signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) txid = self.nodes[0].sendrawtransaction(signedtx['hex']) self.sync_mempools() # Now try to disconnect the tip on each node... self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash()) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) self.sync_blocks()
def run_test(self): nodes = self.nodes ro = nodes[0].extkeyimportmaster( 'abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb' ) assert (ro['account_id'] == 'aaaZf2qnNr5T7PWRmqgmusuu5ACnBcX2ev') assert (nodes[0].getwalletinfo()['total_balance'] == 100000) addrs = [] pubkeys = [] ro = nodes[0].getnewaddress() addrs.append(ro) pubkeys.append(nodes[0].getaddressinfo(ro)['pubkey']) ro = nodes[0].getnewaddress() addrs.append(ro) pubkeys.append(nodes[0].getaddressinfo(ro)['pubkey']) nodes[1].extkeyimportmaster( 'drip fog service village program equip minute dentist series hawk crop sphere olympic lazy garbage segment fox library good alley steak jazz force inmate' ) nodes[2].extkeyimportmaster(nodes[2].mnemonic('new', '', 'french')['mnemonic']) ro = nodes[1].getnewaddress() addrs.append(ro) pubkeys.append(nodes[1].getaddressinfo(ro)['pubkey']) ro = nodes[2].getnewaddress() addrs.append(ro) pubkeys.append(nodes[2].getaddressinfo(ro)['pubkey']) v = [addrs[0], addrs[1], pubkeys[2]] msAddr = nodes[0].addmultisigaddress(2, v)['address'] ro = nodes[0].getaddressinfo(msAddr) assert (ro['isscript'] == True) scriptPubKey = ro['scriptPubKey'] redeemScript = ro['hex'] mstxid = nodes[0].sendtoaddress(msAddr, 10) hexfund = nodes[0].gettransaction(mstxid)['hex'] ro = nodes[0].decoderawtransaction(hexfund) fundscriptpubkey = '' fundoutid = -1 for vout in ro['vout']: if not isclose(vout['value'], 10.0): continue fundoutid = vout['n'] fundscriptpubkey = vout['scriptPubKey']['hex'] assert (fundoutid >= 0), "fund output not found" addrTo = nodes[2].getnewaddress() inputs = [{ "txid": mstxid, "vout": fundoutid, "scriptPubKey": fundscriptpubkey, "redeemScript": redeemScript, "amount": 10.0, }] outputs = {addrTo: 2, msAddr: 7.99} hexRaw = nodes[0].createrawtransaction(inputs, outputs) vk0 = nodes[0].dumpprivkey(addrs[0]) signkeys = [ vk0, ] hexRaw1 = nodes[0].signrawtransactionwithkey(hexRaw, signkeys, inputs)['hex'] vk1 = nodes[0].dumpprivkey(addrs[1]) signkeys = [ vk1, ] hexRaw2 = nodes[0].signrawtransactionwithkey(hexRaw1, signkeys, inputs)['hex'] txnid_spendMultisig = nodes[0].sendrawtransaction(hexRaw2) self.stakeBlocks(1) block1_hash = nodes[0].getblockhash(1) ro = nodes[0].getblock(block1_hash) assert (txnid_spendMultisig in ro['tx']) msAddr256 = nodes[0].addmultisigaddress(2, v, "", False, True)['address'] ro = nodes[0].getaddressinfo(msAddr256) assert (ro['isscript'] == True) msAddr256 = nodes[0].addmultisigaddress(2, v, "", True, True)['address'] assert ( msAddr256 == "tpj1vtll9wnsd7dxzygrjp2j5jr5tgrjsjmj3vwjf7vf60f9p50g5ddqmasmut") ro = nodes[0].getaddressinfo(msAddr256) assert (ro['isscript'] == True) scriptPubKey = ro['scriptPubKey'] redeemScript = ro['hex'] mstxid2 = nodes[0].sendtoaddress(msAddr256, 9) hexfund = nodes[0].gettransaction(mstxid2)['hex'] ro = nodes[0].decoderawtransaction(hexfund) fundscriptpubkey = '' fundoutid = -1 for vout in ro['vout']: if not isclose(vout['value'], 9.0): continue fundoutid = vout['n'] fundscriptpubkey = vout['scriptPubKey']['hex'] assert ('OP_SHA256' in vout['scriptPubKey']['asm']) assert (fundoutid >= 0), "fund output not found" inputs = [{ "txid": mstxid2, "vout": fundoutid, "scriptPubKey": fundscriptpubkey, "redeemScript": redeemScript, "amount": 9.0, # Must specify amount }] addrTo = nodes[2].getnewaddress() outputs = {addrTo: 2, msAddr256: 6.99} hexRaw = nodes[0].createrawtransaction(inputs, outputs) vk0 = nodes[0].dumpprivkey(addrs[0]) signkeys = [ vk0, ] hexRaw1 = nodes[0].signrawtransactionwithkey(hexRaw, signkeys, inputs)['hex'] vk1 = nodes[0].dumpprivkey(addrs[1]) signkeys = [ vk1, ] hexRaw2 = nodes[0].signrawtransactionwithkey(hexRaw1, signkeys, inputs)['hex'] txnid_spendMultisig2 = nodes[0].sendrawtransaction(hexRaw2) self.stakeBlocks(1) block2_hash = nodes[0].getblockhash(2) ro = nodes[0].getblock(block2_hash) assert (txnid_spendMultisig2 in ro['tx']) ro = nodes[0].getaddressinfo(msAddr) scriptPubKey = ro['scriptPubKey'] redeemScript = ro['hex'] opts = {"recipe": "abslocktime", "time": 946684800, "addr": msAddr} scriptTo = nodes[0].buildscript(opts)['hex'] outputs = [ { 'address': 'script', 'amount': 8, 'script': scriptTo }, ] mstxid3 = nodes[0].sendtypeto('part', 'part', outputs) hexfund = nodes[0].gettransaction(mstxid3)['hex'] ro = nodes[0].decoderawtransaction(hexfund) fundscriptpubkey = '' fundoutid = -1 for vout in ro['vout']: if not isclose(vout['value'], 8.0): continue fundoutid = vout['n'] fundscriptpubkey = vout['scriptPubKey']['hex'] assert ('OP_CHECKLOCKTIMEVERIFY' in vout['scriptPubKey']['asm']) assert (fundoutid >= 0), "fund output not found" inputs = [{ "txid": mstxid3, "vout": fundoutid, "scriptPubKey": fundscriptpubkey, "redeemScript": redeemScript, "amount": 8.0, # Must specify amount }] addrTo = nodes[2].getnewaddress() outputs = {addrTo: 2, msAddr: 5.99} locktime = 946684801 hexRaw = nodes[0].createrawtransaction(inputs, outputs, locktime) vk0 = nodes[0].dumpprivkey(addrs[0]) signkeys = [ vk0, ] hexRaw1 = nodes[0].signrawtransactionwithkey(hexRaw, signkeys, inputs)['hex'] vk1 = nodes[0].dumpprivkey(addrs[1]) signkeys = [ vk1, ] hexRaw2 = nodes[0].signrawtransactionwithkey(hexRaw1, signkeys, inputs)['hex'] txnid_spendMultisig3 = nodes[0].sendrawtransaction(hexRaw2) self.stakeBlocks(1) block3_hash = nodes[0].getblockhash(3) ro = nodes[0].getblock(block3_hash) assert (txnid_spendMultisig3 in ro['tx']) self.log.info("Coldstake script") stakeAddr = nodes[0].getnewaddress() addrTo = nodes[0].getnewaddress() opts = { "recipe": "ifcoinstake", "addrstake": stakeAddr, "addrspend": msAddr } scriptTo = nodes[0].buildscript(opts)['hex'] outputs = [{'address': 'script', 'amount': 1, 'script': scriptTo}] txFundId = nodes[0].sendtypeto('part', 'part', outputs) hexfund = nodes[0].gettransaction(txFundId)['hex'] ro = nodes[0].decoderawtransaction(hexfund) for vout in ro['vout']: if not isclose(vout['value'], 1.0): continue fundoutn = vout['n'] fundscriptpubkey = vout['scriptPubKey']['hex'] assert ('OP_ISCOINSTAKE' in vout['scriptPubKey']['asm']) assert (fundoutn >= 0), "fund output not found" ro = nodes[0].getaddressinfo(msAddr) assert (ro['isscript'] == True) scriptPubKey = ro['scriptPubKey'] redeemScript = ro['hex'] inputs = [{ 'txid': txFundId, 'vout': fundoutn, 'scriptPubKey': fundscriptpubkey, 'redeemScript': redeemScript, 'amount': 1.0, }] outputs = {addrTo: 0.99} hexRaw = nodes[0].createrawtransaction(inputs, outputs) sig0 = nodes[0].createsignaturewithwallet(hexRaw, inputs[0], addrs[0]) sig1 = nodes[0].createsignaturewithwallet(hexRaw, inputs[0], addrs[1]) self.log.info( 'Test createsignaturewithwallet without providing prevout details') outpoint_only = {'txid': txFundId, 'vout': fundoutn} sig1_check1 = nodes[0].createsignaturewithwallet( hexRaw, outpoint_only, addrs[1]) assert (sig1 == sig1_check1) addr1_privkey = nodes[0].dumpprivkey(addrs[1]) sig1_check2 = nodes[0].createsignaturewithkey(hexRaw, inputs[0], addr1_privkey) assert (sig1 == sig1_check2) try: sig1_check3 = nodes[0].createsignaturewithkey( hexRaw, outpoint_only, addr1_privkey) assert ( False), 'createsignaturewithkey passed with no redeemscript' except JSONRPCException as e: assert ('"redeemScript" is required' in e.error['message']) outpoint_only['redeemScript'] = redeemScript sig1_check3 = nodes[0].createsignaturewithkey(hexRaw, outpoint_only, addr1_privkey) assert (sig1 == sig1_check3) witnessStack = [ "", sig0, sig1, redeemScript, ] hexRawSigned = nodes[0].tx( [hexRaw, 'witness=0:' + ':'.join(witnessStack)]) ro = nodes[0].verifyrawtransaction(hexRawSigned) assert (ro['complete'] == True) ro = nodes[0].signrawtransactionwithwallet(hexRaw) assert (ro['complete'] == True) assert (ro['hex'] == hexRawSigned) txid = nodes[0].sendrawtransaction(hexRawSigned) self.stakeBlocks(1) block4_hash = nodes[0].getblockhash(4) ro = nodes[0].getblock(block4_hash) assert (txid in ro['tx']) self.log.info("Test combinerawtransaction") unspent0 = nodes[0].listunspent() unspent2 = nodes[2].listunspent() inputs = [unspent0[0], unspent2[0]] outputs = { nodes[0].getnewaddress(): satoshi_round(unspent0[0]['amount'] + unspent2[0]['amount'] - Decimal(0.1)) } rawtx = nodes[0].createrawtransaction(inputs, outputs) rawtx0 = nodes[0].signrawtransactionwithwallet(rawtx) assert (rawtx0['complete'] == False) rawtx2 = nodes[2].signrawtransactionwithwallet( rawtx0['hex']) # Keeps signature from node0 assert (rawtx2['complete']) rawtx2 = nodes[2].signrawtransactionwithwallet(rawtx) assert (rawtx2['complete'] == False) rawtx_complete = nodes[0].combinerawtransaction( [rawtx0['hex'], rawtx2['hex']]) nodes[0].sendrawtransaction(rawtx_complete)
def run_test(self): self.enable_mocktime() self.setup_3_masternodes_network() txHashSet = set([ self.mnOneCollateral.hash, self.mnTwoCollateral.hash, self.proRegTx1 ]) # check mn list from miner self.check_mn_list(self.miner, txHashSet) # check status of masternodes self.check_mns_status_legacy(self.remoteOne, self.mnOneCollateral.hash) self.log.info("MN1 active") self.check_mns_status_legacy(self.remoteTwo, self.mnTwoCollateral.hash) self.log.info("MN2 active") self.check_mns_status(self.remoteDMN1, self.proRegTx1) self.log.info("DMN1 active") # Prepare the proposal self.log.info("preparing budget proposal..") firstProposalName = "super-cool" firstProposalLink = "https://forum.pivx.org/t/test-proposal" firstProposalCycles = 2 firstProposalAddress = self.miner.getnewaddress() firstProposalAmountPerCycle = 300 nextSuperBlockHeight = self.miner.getnextsuperblock() proposalFeeTxId = self.miner.preparebudget( firstProposalName, firstProposalLink, firstProposalCycles, nextSuperBlockHeight, firstProposalAddress, firstProposalAmountPerCycle) # generate 3 blocks to confirm the tx (and update the mnping) self.stake(3, [self.remoteOne, self.remoteTwo]) # activate sporks self.activate_spork(self.minerPos, "SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT") self.activate_spork(self.minerPos, "SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT") self.activate_spork(self.minerPos, "SPORK_13_ENABLE_SUPERBLOCKS") txinfo = self.miner.gettransaction(proposalFeeTxId) assert_equal(txinfo['amount'], -50.00) self.log.info("submitting the budget proposal..") proposalHash = self.miner.submitbudget( firstProposalName, firstProposalLink, firstProposalCycles, nextSuperBlockHeight, firstProposalAddress, firstProposalAmountPerCycle, proposalFeeTxId) # let's wait a little bit and see if all nodes are sync time.sleep(1) self.check_proposal_existence(firstProposalName, proposalHash) self.log.info("proposal broadcast successful!") # Proposal is established after 5 minutes. Mine 7 blocks # Proposal needs to be on the chain > 5 min. self.stake(7, [self.remoteOne, self.remoteTwo]) # now let's vote for the proposal with the first MN self.log.info("Voting with MN1...") voteResult = self.ownerOne.mnbudgetvote("alias", proposalHash, "yes", self.masternodeOneAlias, True) assert_equal(voteResult["detail"][0]["result"], "success") # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) self.check_vote_existence(firstProposalName, self.mnOneCollateral.hash, "YES", True) self.log.info("all good, MN1 vote accepted everywhere!") # now let's vote for the proposal with the second MN self.log.info("Voting with MN2...") voteResult = self.ownerTwo.mnbudgetvote("alias", proposalHash, "yes", self.masternodeTwoAlias, True) assert_equal(voteResult["detail"][0]["result"], "success") # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) self.check_vote_existence(firstProposalName, self.mnTwoCollateral.hash, "YES", True) self.log.info("all good, MN2 vote accepted everywhere!") # now let's vote for the proposal with the first DMN self.log.info("Voting with DMN1...") voteResult = self.ownerOne.mnbudgetvote("alias", proposalHash, "yes", self.proRegTx1) assert_equal(voteResult["detail"][0]["result"], "success") # check that the vote was accepted everywhere self.stake(1, [self.remoteOne, self.remoteTwo]) self.check_vote_existence(firstProposalName, self.proRegTx1, "YES", True) self.log.info("all good, DMN1 vote accepted everywhere!") # Now check the budget blockStart = nextSuperBlockHeight blockEnd = blockStart + firstProposalCycles * 145 TotalPayment = firstProposalAmountPerCycle * firstProposalCycles Allotted = firstProposalAmountPerCycle RemainingPaymentCount = firstProposalCycles expected_budget = [ self.get_proposal_obj(firstProposalName, firstProposalLink, proposalHash, proposalFeeTxId, blockStart, blockEnd, firstProposalCycles, RemainingPaymentCount, firstProposalAddress, 1, 3, 0, 0, satoshi_round(TotalPayment), satoshi_round(firstProposalAmountPerCycle), True, True, satoshi_round(Allotted), satoshi_round(Allotted)) ] self.check_budgetprojection(expected_budget) # Quick block count check. assert_equal(self.ownerOne.getblockcount(), 276) self.log.info("starting budget finalization sync test..") self.stake(5, [self.remoteOne, self.remoteTwo]) # assert that there is no budget finalization first. assert_true(len(self.ownerOne.mnfinalbudget("show")) == 0) # suggest the budget finalization and confirm the tx (+4 blocks). budgetFinHash = self.broadcastbudgetfinalization( self.miner, with_ping_mns=[self.remoteOne, self.remoteTwo]) assert (budgetFinHash != "") time.sleep(1) self.log.info("checking budget finalization sync..") self.check_budget_finalization_sync(0, "OK") self.log.info( "budget finalization synced!, now voting for the budget finalization.." ) voteResult = self.ownerOne.mnfinalbudget("vote-many", budgetFinHash, True) assert_equal(voteResult["detail"][0]["result"], "success") self.log.info("Remote One voted successfully.") voteResult = self.ownerTwo.mnfinalbudget("vote-many", budgetFinHash, True) assert_equal(voteResult["detail"][0]["result"], "success") self.log.info("Remote Two voted successfully.") voteResult = self.remoteDMN1.mnfinalbudget("vote", budgetFinHash) assert_equal(voteResult["detail"][0]["result"], "success") self.log.info("DMN voted successfully.") self.stake(2, [self.remoteOne, self.remoteTwo]) self.log.info("checking finalization votes..") self.check_budget_finalization_sync(3, "OK") self.stake(8, [self.remoteOne, self.remoteTwo]) addrInfo = self.miner.listreceivedbyaddress(0, False, False, firstProposalAddress) assert_equal(addrInfo[0]["amount"], firstProposalAmountPerCycle) self.log.info("budget proposal paid!, all good") # Check that the proposal info returns updated payment count expected_budget[0]["RemainingPaymentCount"] -= 1 self.check_budgetprojection(expected_budget) self.stake(1, [self.remoteOne, self.remoteTwo]) # now let's verify that votes expire properly. # Drop one MN and one DMN self.log.info("expiring MN1..") self.spend_collateral(self.ownerOne, self.mnOneCollateral, self.miner) self.wait_until_mn_vinspent(self.mnOneCollateral.hash, 30, [self.remoteTwo]) self.stake(15, [self.remoteTwo]) # create blocks to remove staled votes time.sleep(2) # wait a little bit self.check_vote_existence(firstProposalName, self.mnOneCollateral.hash, "YES", False) self.log.info("MN1 vote expired after collateral spend, all good") self.log.info("expiring DMN1..") lm = self.ownerOne.listmasternodes(self.proRegTx1)[0] self.spend_collateral( self.ownerOne, COutPoint(lm["collateralHash"], lm["collateralIndex"]), self.miner) self.wait_until_mn_vinspent(self.proRegTx1, 30, [self.remoteTwo]) self.stake(15, [self.remoteTwo]) # create blocks to remove staled votes time.sleep(2) # wait a little bit self.check_vote_existence(firstProposalName, self.proRegTx1, "YES", False) self.log.info("DMN vote expired after collateral spend, all good")
def run_test(self): node = self.nodes[0] node.spork("SPORK_17_QUORUM_DKG_ENABLED", 0) self.wait_for_sporks_same() self.mine_quorum() txid = node.sendtoaddress(node.getnewaddress(), 1) self.wait_for_instantlock(txid, node) request_id = self.get_request_id(self.nodes[0].getrawtransaction(txid)) wait_until(lambda: node.quorum("hasrecsig", 100, request_id, txid)) rec_sig = node.quorum("getrecsig", 100, request_id, txid)['sig'] assert (node.verifyislock(request_id, txid, rec_sig)) # Not mined, should use maxHeight assert not node.verifyislock(request_id, txid, rec_sig, 1) node.generate(1) assert (txid not in node.getrawmempool()) # Mined but at higher height, should use maxHeight assert not node.verifyislock(request_id, txid, rec_sig, 1) # Mined, should ignore higher maxHeight assert (node.verifyislock(request_id, txid, rec_sig, node.getblockcount() + 100)) # Mine one more quorum to have a full active set self.mine_quorum() # Create an ISLOCK for the oldest quorum i.e. the active quorum which will be moved # out of the active set when a new quorum appears selected_hash = None request_id = None oldest_quorum_hash = node.quorum("list")["llmq_test"][-1] utxos = node.listunspent() fee = 0.001 amount = 1 # Try all available utxo's until we have one resulting in a request id which selects the # last active quorum for utxo in utxos: in_amount = float(utxo['amount']) if in_amount < amount + fee: continue outputs = dict() outputs[node.getnewaddress()] = satoshi_round(amount) change = in_amount - amount - fee if change > 0: outputs[node.getnewaddress()] = satoshi_round(change) rawtx = node.createrawtransaction([utxo], outputs) rawtx = node.signrawtransactionwithwallet(rawtx)["hex"] request_id = self.get_request_id(rawtx) selected_hash = node.quorum('selectquorum', 100, request_id)["quorumHash"] if selected_hash == oldest_quorum_hash: break assert selected_hash == oldest_quorum_hash # Create the ISLOCK, then mine a quorum to move the signing quorum out of the active set islock = self.create_islock(rawtx) # Mine one block to trigger the "signHeight + dkgInterval" verification for the ISLOCK self.mine_quorum() # Verify the ISLOCK for a transaction that is not yet known by the node rawtx_txid = node.decoderawtransaction(rawtx)["txid"] assert_raises_rpc_error(-5, "No such mempool or blockchain transaction", node.getrawtransaction, rawtx_txid) assert node.verifyislock(request_id, rawtx_txid, bytes_to_hex_str(islock.sig), node.getblockcount()) # Send the tx and verify the ISLOCK for a now known transaction assert rawtx_txid == node.sendrawtransaction(rawtx) assert node.verifyislock(request_id, rawtx_txid, bytes_to_hex_str(islock.sig), node.getblockcount())
def total_fees(*txids): total = 0 for txid in txids: # '-=' is because gettransaction(txid)['fee'] returns a negative total -= self.nodes[0].gettransaction(txid)['fee'] return satoshi_round(total)
def run_test(self): self.nodes[0].generate(2) self.sync_all() assert_equal(self.nodes[1].getblockcount(), 202) taddr1 = self.nodes[1].getnewaddress() saplingAddr0 = self.nodes[0].getnewshieldaddress() saplingAddr1 = self.nodes[1].getnewshieldaddress() # Verify addresses assert(saplingAddr0 in self.nodes[0].listshieldaddresses()) assert(saplingAddr1 in self.nodes[1].listshieldaddresses()) # Verify balance assert_equal(self.nodes[0].getshieldbalance(saplingAddr0), Decimal('0')) assert_equal(self.nodes[1].getshieldbalance(saplingAddr1), Decimal('0')) assert_equal(self.nodes[1].getreceivedbyaddress(taddr1), Decimal('0')) recipients = [{"address": saplingAddr0, "amount": Decimal('10')}] # Try fee too low fee_too_low = 0.001 self.log.info("Trying to send a transaction with fee too low...") assert_raises_rpc_error(-4, "Fee set (%.3f) too low. Must be at least" % fee_too_low, self.nodes[0].rawshieldsendmany, "from_transparent", recipients, 1, fee_too_low) # Try fee too high. fee_too_high = 20 self.log.info("Good. It was not possible. Now try a tx with fee too high...") assert_raises_rpc_error(-4, "The transaction fee is too high: %.2f >" % fee_too_high, self.nodes[0].rawshieldsendmany, "from_transparent", recipients, 1, fee_too_high) # Trying to send a rawtx with low fee directly self.log.info("Good. It was not possible. Now try with a raw tx...") self.restart_node(0, extra_args=self.extra_args[0]+['-minrelaytxfee=0.0000001']) rawtx_hex = self.nodes[0].rawshieldsendmany("from_transparent", recipients, 1) self.restart_node(0, extra_args=self.extra_args[0]) connect_nodes(self.nodes[0], 1) assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, rawtx_hex) self.log.info("Good. Not accepted in the mempool.") # Fixed fee fee = 0.05 # Node 0 shields some funds # taddr -> Sapling self.log.info("TX 1: shield funds from specified transparent address.") mytxid1 = self.nodes[0].shieldsendmany(get_coinstake_address(self.nodes[0]), recipients, 1, fee) # shield more funds automatically selecting the transparent inputs self.log.info("TX 2: shield funds from any transparent address.") mytxid2 = self.nodes[0].shieldsendmany("from_transparent", recipients, 1, fee) # Verify priority of tx is INF_PRIORITY, defined as 1E+25 (10000000000000000000000000) self.check_tx_priority([mytxid1, mytxid2]) self.log.info("Priority for tx1 and tx2 checks out") self.nodes[2].generate(1) self.sync_all() # shield more funds creating and then sending a raw transaction self.log.info("TX 3: shield funds creating and sending raw transaction.") tx_hex = self.nodes[0].rawshieldsendmany("from_transparent", recipients, 1, fee) # Check SPORK_20 for sapling maintenance mode SPORK_20 = "SPORK_20_SAPLING_MAINTENANCE" self.activate_spork(0, SPORK_20) self.wait_for_spork(True, SPORK_20) assert_raises_rpc_error(-26, "bad-tx-sapling-maintenance", self.nodes[0].sendrawtransaction, tx_hex) self.log.info("Good. Not accepted when SPORK_20 is active.") # Try with RPC... assert_raises_rpc_error(-8, "Invalid parameter, Sapling not active yet", self.nodes[0].shieldsendmany, "from_transparent", recipients, 1, fee) # Disable SPORK_20 and retry sleep(5) self.deactivate_spork(0, SPORK_20) self.wait_for_spork(False, SPORK_20) mytxid3 = self.nodes[0].sendrawtransaction(tx_hex) self.log.info("Good. Accepted when SPORK_20 is not active.") # Verify priority of tx is INF_PRIORITY, defined as 1E+25 (10000000000000000000000000) self.check_tx_priority([mytxid3]) self.log.info("Priority for tx3 checks out") self.nodes[2].generate(1) self.sync_all() # Verify balance assert_equal(self.nodes[0].getshieldbalance(saplingAddr0), Decimal('30')) assert_equal(self.nodes[1].getshieldbalance(saplingAddr1), Decimal('0')) assert_equal(self.nodes[1].getreceivedbyaddress(taddr1), Decimal('0')) self.log.info("Balances check out") # Now disconnect the block, activate SPORK_20, and try to reconnect it disconnect_nodes(self.nodes[0], 1) tip_hash = self.nodes[0].getbestblockhash() self.nodes[0].invalidateblock(tip_hash) assert tip_hash != self.nodes[0].getbestblockhash() assert_equal(self.nodes[0].getshieldbalance(saplingAddr0), Decimal('20')) self.log.info("Now trying to connect block with shield tx, when SPORK_20 is active") self.activate_spork(0, SPORK_20) self.nodes[0].reconsiderblock(tip_hash) assert tip_hash != self.nodes[0].getbestblockhash() # Block NOT connected assert_equal(self.nodes[0].getshieldbalance(saplingAddr0), Decimal('20')) self.log.info("Good. Not possible.") # Deactivate SPORK_20 and reconnect sleep(1) self.deactivate_spork(0, SPORK_20) self.nodes[0].reconsiderblock(tip_hash) self.nodes[0].syncwithvalidationinterfacequeue() assert_equal(tip_hash, self.nodes[0].getbestblockhash()) # Block connected assert_equal(self.nodes[0].getshieldbalance(saplingAddr0), Decimal('30')) self.log.info("Reconnected after deactivation of SPORK_20. Balance restored.") connect_nodes(self.nodes[0], 1) # Node 0 sends some shield funds to node 1 # Sapling -> Sapling # -> Sapling (change) self.log.info("TX 4: shield transaction from specified sapling address.") recipients4 = [{"address": saplingAddr1, "amount": Decimal('10')}] mytxid4 = self.nodes[0].shieldsendmany(saplingAddr0, recipients4, 1, fee) self.check_tx_priority([mytxid4]) self.nodes[2].generate(1) self.sync_all() # Send more shield funds (this time with automatic selection of the source) self.log.info("TX 5: shield transaction from any sapling address.") recipients5 = [{"address": saplingAddr1, "amount": Decimal('5')}] mytxid5 = self.nodes[0].shieldsendmany("from_shield", recipients5, 1, fee) self.check_tx_priority([mytxid5]) self.nodes[2].generate(1) self.sync_all() # Send more shield funds (with create + send raw transaction) self.log.info("TX 6: shield raw transaction.") tx_hex = self.nodes[0].rawshieldsendmany("from_shield", recipients5, 1, fee) mytxid6 = self.nodes[0].sendrawtransaction(tx_hex) self.check_tx_priority([mytxid6]) self.nodes[2].generate(1) self.sync_all() # Shield more funds to a different address to verify multi-source notes spending saplingAddr2 = self.nodes[0].getnewshieldaddress() self.log.info("TX 7: shield funds to later verify multi source notes spending.") recipients = [{"address": saplingAddr2, "amount": Decimal('10')}] mytxid7 = self.nodes[0].shieldsendmany(get_coinstake_address(self.nodes[0]), recipients, 1, fee) self.check_tx_priority([mytxid7]) self.nodes[2].generate(5) self.sync_all() # Verify multi-source notes spending tAddr0 = self.nodes[0].getnewaddress() self.log.info("TX 8: verifying multi source notes spending.") recipients = [{"address": tAddr0, "amount": Decimal('11')}] mytxid8 = self.nodes[0].shieldsendmany("from_shield", recipients, 1, fee) self.check_tx_priority([mytxid8]) self.nodes[2].generate(1) self.sync_all() # Verify balance assert_equal(self.nodes[0].getshieldbalance(saplingAddr0), Decimal('4.9')) # 30 received - (20 sent + 0.15 fee) - 4.95 sent assert_equal(self.nodes[1].getshieldbalance(saplingAddr1), Decimal('20')) # 20 received assert_equal(self.nodes[0].getshieldbalance(saplingAddr2), Decimal('3.9')) # 10 received - 10 sent + 3.9 change assert_equal(self.nodes[1].getreceivedbyaddress(taddr1), Decimal('0')) assert_equal(self.nodes[0].getshieldbalance(), Decimal('8.8')) self.log.info("Balances check out") # Node 1 sends some shield funds to node 0, as well as unshielding # Sapling -> Sapling # -> taddr # -> Sapling (change) self.log.info("TX 10: deshield funds from specified sapling address.") recipients7 = [{"address": saplingAddr0, "amount": Decimal('8')}] recipients7.append({"address": taddr1, "amount": Decimal('10')}) mytxid7 = self.nodes[1].shieldsendmany(saplingAddr1, recipients7, 1, fee) self.check_tx_priority([mytxid7]) self.nodes[2].generate(1) self.sync_all() # Verify balance assert_equal(self.nodes[0].getshieldbalance(saplingAddr0), Decimal('12.9')) # 4.9 prev balance + 8 received assert_equal(self.nodes[1].getshieldbalance(saplingAddr1), Decimal('1.95')) # 20 prev balance - (18 sent + 0.05 fee) assert_equal(self.nodes[1].getreceivedbyaddress(taddr1), Decimal('10')) self.log.info("Balances check out") # Verify existence of Sapling related JSON fields resp = self.nodes[0].getrawtransaction(mytxid7, 1) assert_equal(Decimal(resp['valueBalance']), Decimal('10.05')) # 20 shield input - 8 shield spend - 1.95 change assert_equal(len(resp['vShieldSpend']), 3) assert_equal(len(resp['vShieldOutput']), 2) assert('bindingSig' in resp) shieldedSpend = resp['vShieldSpend'][0] assert('cv' in shieldedSpend) assert('anchor' in shieldedSpend) assert('nullifier' in shieldedSpend) assert('rk' in shieldedSpend) assert('proof' in shieldedSpend) assert('spendAuthSig' in shieldedSpend) shieldedOutput = resp['vShieldOutput'][0] assert('cv' in shieldedOutput) assert('cmu' in shieldedOutput) assert('ephemeralKey' in shieldedOutput) assert('encCiphertext' in shieldedOutput) assert('outCiphertext' in shieldedOutput) assert('proof' in shieldedOutput) self.log.info("Raw transaction decoding checks out") # Verify importing a spending key will update the nullifiers and witnesses correctly self.log.info("Checking exporting/importing a spending key...") sk0 = self.nodes[0].exportsaplingkey(saplingAddr0) saplingAddrInfo0 = self.nodes[2].importsaplingkey(sk0, "yes") assert_equal(saplingAddrInfo0["address"], saplingAddr0) assert_equal(self.nodes[2].getshieldbalance(saplingAddrInfo0["address"]), Decimal('12.9')) sk1 = self.nodes[1].exportsaplingkey(saplingAddr1) saplingAddrInfo1 = self.nodes[2].importsaplingkey(sk1, "yes") assert_equal(saplingAddrInfo1["address"], saplingAddr1) assert_equal(self.nodes[2].getshieldbalance(saplingAddrInfo1["address"]), Decimal('1.95')) # Verify importing a viewing key will update the nullifiers and witnesses correctly self.log.info("Checking exporting/importing a viewing key...") extfvk0 = self.nodes[0].exportsaplingviewingkey(saplingAddr0) saplingAddrInfo0 = self.nodes[3].importsaplingviewingkey(extfvk0, "yes") assert_equal(saplingAddrInfo0["address"], saplingAddr0) assert_equal(Decimal(self.nodes[3].getshieldbalance(saplingAddrInfo0["address"], 1, True)), Decimal('12.9')) extfvk1 = self.nodes[1].exportsaplingviewingkey(saplingAddr1) saplingAddrInfo1 = self.nodes[3].importsaplingviewingkey(extfvk1, "yes") assert_equal(saplingAddrInfo1["address"], saplingAddr1) assert_equal(self.nodes[3].getshieldbalance(saplingAddrInfo1["address"], 1, True), Decimal('1.95')) # no balance in the wallet assert_equal(self.nodes[3].getshieldbalance(), Decimal('0')) # watch only balance assert_equal(self.nodes[3].getshieldbalance("*", 1, True), Decimal('14.85')) # Now shield some funds using sendmany self.log.info("TX11: Shielding coins to multiple destinations with sendmany RPC...") prev_balance = self.nodes[0].getbalance() recipients8 = {saplingAddr0: Decimal('8'), saplingAddr1: Decimal('1'), saplingAddr2: Decimal('0.5')} mytxid11 = self.nodes[0].sendmany("", recipients8) self.check_tx_priority([mytxid11]) self.log.info("Done. Checking details and balances...") # Decrypted transaction details should be correct pt = self.nodes[0].viewshieldtransaction(mytxid11) fee = pt["fee"] assert_equal(pt['txid'], mytxid11) assert_equal(len(pt['spends']), 0) assert_equal(len(pt['outputs']), 3) found = [False] * 3 for out in pt['outputs']: assert_equal(pt['outputs'].index(out), out['output']) if out['address'] == saplingAddr0: assert_equal(out['outgoing'], False) assert_equal(out['value'], Decimal('8')) found[0] = True elif out['address'] == saplingAddr1: assert_equal(out['outgoing'], True) assert_equal(out['value'], Decimal('1')) found[1] = True else: assert_equal(out['address'], saplingAddr2) assert_equal(out['outgoing'], False) assert_equal(out['value'], Decimal('0.5')) found[2] = True assert_equal(found, [True] * 3) # Verify balance self.nodes[2].generate(1) self.sync_all() assert_equal(self.nodes[0].getshieldbalance(saplingAddr0), Decimal('20.9')) # 12.9 prev balance + 8 received assert_equal(self.nodes[1].getshieldbalance(saplingAddr1), Decimal('2.95')) # 1.95 prev balance + 1 received assert_equal(self.nodes[0].getshieldbalance(saplingAddr2), Decimal('4.4')) # 3.9 prev balance + 0.5 received # Balance of node 0 is: prev_balance - 1 QRTC (+fee) sent externally + 250 QRTC matured coinbase assert_equal(self.nodes[0].getbalance(), satoshi_round(prev_balance + Decimal('249') - Decimal(fee))) # Now shield some funds using sendtoaddress self.log.info("TX12: Shielding coins with sendtoaddress RPC...") prev_balance = self.nodes[0].getbalance() mytxid12 = self.nodes[0].sendtoaddress(saplingAddr0, Decimal('10')) self.check_tx_priority([mytxid12]) self.log.info("Done. Checking details and balances...") # Decrypted transaction details should be correct pt = self.nodes[0].viewshieldtransaction(mytxid12) fee = pt["fee"] assert_equal(pt['txid'], mytxid12) assert_equal(len(pt['spends']), 0) assert_equal(len(pt['outputs']), 1) out = pt['outputs'][0] assert_equal(out['address'], saplingAddr0) assert_equal(out['outgoing'], False) assert_equal(out['value'], Decimal('10')) # Verify balance self.nodes[2].generate(1) self.sync_all() assert_equal(self.nodes[0].getshieldbalance(saplingAddr0), Decimal('30.9')) # 20.9 prev balance + 10 received self.log.info("All good.")
def test_compactblock_construction(self, node, test_node, version, use_witness_address): # Generate a bunch of transactions. node.generate(101) num_transactions = 25 address = node.getnewaddress() if use_witness_address: # Want at least one segwit spend, so move all funds to # a witness address. address = node.addwitnessaddress(address) value_to_send = node.getbalance() node.sendtoaddress(address, satoshi_round(value_to_send-Decimal(0.1))) node.generate(1) segwit_tx_generated = False for i in range(num_transactions): txid = node.sendtoaddress(address, 0.1) hex_tx = node.gettransaction(txid)["hex"] tx = FromHex(CTransaction(), hex_tx) if not tx.wit.is_null(): segwit_tx_generated = True if use_witness_address: assert(segwit_tx_generated) # check that our test is not broken # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) test_node.wait_for_block_announcement(tip) # Make sure we will receive a fast-announce compact block self.request_cb_announcements(test_node, node, version) # Now mine a block, and look at the resulting compact block. test_node.clear_block_announcement() block_hash = int(node.generate(1)[0], 16) # Store the raw block in our internal format. block = FromHex(CBlock(), node.getblock("%02x" % block_hash, False)) for tx in block.vtx: tx.calc_sha256() block.rehash() # Wait until the block was announced (via compact blocks) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert("cmpctblock" in test_node.last_message) # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs(test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block) # Now fetch the compact block using a normal non-announce getdata with mininode_lock: test_node.clear_block_announcement() inv = CInv(4, block_hash) # 4 == "CompactBlock" test_node.send_message(msg_getdata([inv])) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert("cmpctblock" in test_node.last_message) # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs(test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block)
def run_test(self): # Mine some blocks and have them mature. self.nodes[0].generate(101) utxo = self.nodes[0].listunspent(10) txid = utxo[0]['txid'] vout = utxo[0]['vout'] value = utxo[0]['amount'] fee = Decimal("0.0001") # MAX_ANCESTORS transactions off a confirmed tx should be fine chain = [] for i in range(MAX_ANCESTORS): (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, 0, value, fee, 1) value = sent_value chain.append(txid) # Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor # count and fees should look correct mempool = self.nodes[0].getrawmempool(True) assert_equal(len(mempool), MAX_ANCESTORS) descendant_count = 1 descendant_fees = 0 descendant_size = 0 ancestor_size = sum([mempool[tx]['size'] for tx in mempool]) ancestor_count = MAX_ANCESTORS ancestor_fees = sum([mempool[tx]['fee'] for tx in mempool]) descendants = [] ancestors = list(chain) for x in reversed(chain): # Check that getmempoolentry is consistent with getrawmempool entry = self.nodes[0].getmempoolentry(x) assert_equal(entry, mempool[x]) # Check that the descendant calculations are correct assert_equal(mempool[x]['descendantcount'], descendant_count) descendant_fees += mempool[x]['fee'] assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']) assert_equal(mempool[x]['fees']['base'], mempool[x]['fee']) assert_equal(mempool[x]['fees']['modified'], mempool[x]['modifiedfee']) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN) assert_equal(mempool[x]['fees']['descendant'], descendant_fees) descendant_size += mempool[x]['size'] assert_equal(mempool[x]['descendantsize'], descendant_size) descendant_count += 1 # Check that ancestor calculations are correct assert_equal(mempool[x]['ancestorcount'], ancestor_count) assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN) assert_equal(mempool[x]['ancestorsize'], ancestor_size) ancestor_size -= mempool[x]['size'] ancestor_fees -= mempool[x]['fee'] ancestor_count -= 1 # Check that parent/child list is correct assert_equal(mempool[x]['spentby'], descendants[-1:]) assert_equal(mempool[x]['depends'], ancestors[-2:-1]) # Check that getmempooldescendants is correct assert_equal(sorted(descendants), sorted(self.nodes[0].getmempooldescendants(x))) # Check getmempooldescendants verbose output is correct for descendant, dinfo in self.nodes[0].getmempooldescendants( x, True).items(): assert_equal(dinfo['depends'], [chain[chain.index(descendant) - 1]]) if dinfo['descendantcount'] > 1: assert_equal(dinfo['spentby'], [chain[chain.index(descendant) + 1]]) else: assert_equal(dinfo['spentby'], []) descendants.append(x) # Check that getmempoolancestors is correct ancestors.remove(x) assert_equal(sorted(ancestors), sorted(self.nodes[0].getmempoolancestors(x))) # Check that getmempoolancestors verbose output is correct for ancestor, ainfo in self.nodes[0].getmempoolancestors( x, True).items(): assert_equal(ainfo['spentby'], [chain[chain.index(ancestor) + 1]]) if ainfo['ancestorcount'] > 1: assert_equal(ainfo['depends'], [chain[chain.index(ancestor) - 1]]) else: assert_equal(ainfo['depends'], []) # Check that getmempoolancestors/getmempooldescendants correctly handle verbose=true v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True) assert_equal(len(v_ancestors), len(chain) - 1) for x in v_ancestors.keys(): assert_equal(mempool[x], v_ancestors[x]) assert (chain[-1] not in v_ancestors.keys()) v_descendants = self.nodes[0].getmempooldescendants(chain[0], True) assert_equal(len(v_descendants), len(chain) - 1) for x in v_descendants.keys(): assert_equal(mempool[x], v_descendants[x]) assert (chain[0] not in v_descendants.keys()) # Check that ancestor modified fees includes fee deltas from # prioritisetransaction self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=1000) mempool = self.nodes[0].getrawmempool(True) ancestor_fees = 0 for x in chain: ancestor_fees += mempool[x]['fee'] assert_equal(mempool[x]['fees']['ancestor'], ancestor_fees + Decimal('0.00001')) assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN + 1000) # Undo the prioritisetransaction for later tests self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=-1000) # Check that descendant modified fees includes fee deltas from # prioritisetransaction self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=1000) mempool = self.nodes[0].getrawmempool(True) descendant_fees = 0 for x in reversed(chain): descendant_fees += mempool[x]['fee'] assert_equal(mempool[x]['fees']['descendant'], descendant_fees + Decimal('0.00001')) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 1000) # Adding one more transaction on to the chain should fail. assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], txid, vout, value, fee, 1) # Check that prioritising a tx before it's added to the mempool works # First clear the mempool by mining a block. self.nodes[0].generate(1) sync_blocks(self.nodes) assert_equal(len(self.nodes[0].getrawmempool()), 0) # Prioritise a transaction that has been mined, then add it back to the # mempool by using invalidateblock. self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=2000) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # Keep node1's tip synced with node0 self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash()) # Now check that the transaction is in the mempool, with the right modified fee mempool = self.nodes[0].getrawmempool(True) descendant_fees = 0 for x in reversed(chain): descendant_fees += mempool[x]['fee'] if (x == chain[-1]): assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee'] + satoshi_round(0.00002)) assert_equal(mempool[x]['fees']['modified'], mempool[x]['fee'] + satoshi_round(0.00002)) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 2000) assert_equal(mempool[x]['fees']['descendant'], descendant_fees + satoshi_round(0.00002)) # TODO: check that node1's mempool is as expected # TODO: test ancestor size limits # Now test descendant chain limits txid = utxo[1]['txid'] value = utxo[1]['amount'] vout = utxo[1]['vout'] transaction_package = [] tx_children = [] # First create one parent tx with 10 children (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 10) parent_transaction = txid for i in range(10): transaction_package.append({ 'txid': txid, 'vout': i, 'amount': sent_value }) # Sign and send up to MAX_DESCENDANT transactions chained off the parent tx for i in range(MAX_DESCENDANTS - 1): utxo = transaction_package.pop(0) (txid, sent_value) = self.chain_transaction(self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) if utxo['txid'] is parent_transaction: tx_children.append(txid) for j in range(10): transaction_package.append({ 'txid': txid, 'vout': j, 'amount': sent_value }) mempool = self.nodes[0].getrawmempool(True) assert_equal(mempool[parent_transaction]['descendantcount'], MAX_DESCENDANTS) assert_equal(sorted(mempool[parent_transaction]['spentby']), sorted(tx_children)) for child in tx_children: assert_equal(mempool[child]['depends'], [parent_transaction]) # Sending one more chained transaction will fail utxo = transaction_package.pop(0) assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) # TODO: check that node1's mempool is as expected # TODO: test descendant size limits # Test reorg handling # First, the basics: self.nodes[0].generate(1) sync_blocks(self.nodes) self.nodes[1].invalidateblock(self.nodes[0].getbestblockhash()) self.nodes[1].reconsiderblock(self.nodes[0].getbestblockhash()) # Now test the case where node1 has a transaction T in its mempool that # depends on transactions A and B which are in a mined block, and the # block containing A and B is disconnected, AND B is not accepted back # into node1's mempool because its ancestor count is too high. # Create 8 transactions, like so: # Tx0 -> Tx1 (vout0) # \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7 # # Mine them in the next block, then generate a new tx8 that spends # Tx1 and Tx7, and add to node1's mempool, then disconnect the # last block. # Create tx0 with 2 outputs utxo = self.nodes[0].listunspent() txid = utxo[0]['txid'] value = utxo[0]['amount'] vout = utxo[0]['vout'] send_value = satoshi_round((value - fee) / 2) inputs = [{'txid': txid, 'vout': vout}] outputs = {} for i in range(2): outputs[self.nodes[0].getnewaddress()] = send_value rawtx = self.nodes[0].createrawtransaction(inputs, outputs) signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) txid = self.nodes[0].sendrawtransaction(signedtx['hex']) tx0_id = txid value = send_value # Create tx1 tx1_id, _ = self.chain_transaction(self.nodes[0], tx0_id, 0, value, fee, 1) # Create tx2-7 vout = 1 txid = tx0_id for i in range(6): (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 1) vout = 0 value = sent_value # Mine these in a block self.nodes[0].generate(1) self.sync_all() # Now generate tx8, with a big fee inputs = [{'txid': tx1_id, 'vout': 0}, {'txid': txid, 'vout': 0}] outputs = {self.nodes[0].getnewaddress(): send_value + value - 4 * fee} rawtx = self.nodes[0].createrawtransaction(inputs, outputs) signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) txid = self.nodes[0].sendrawtransaction(signedtx['hex']) sync_mempools(self.nodes) # Now try to disconnect the tip on each node... self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash()) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) sync_blocks(self.nodes)
def run_test(self): # Mine some blocks and have them mature. self.nodes[0].generate(101) utxo = self.nodes[0].listunspent(10) txid = utxo[0]['txid'] vout = utxo[0]['vout'] value = utxo[0]['amount'] fee = Decimal("0.0001") # MAX_ANCESTORS transactions off a confirmed tx should be fine chain = [] for i in range(MAX_ANCESTORS): (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, 0, value, fee, 1) value = sent_value chain.append(txid) # Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor # count and fees should look correct mempool = self.nodes[0].getrawmempool(True) assert_equal(len(mempool), MAX_ANCESTORS) descendant_count = 1 descendant_fees = 0 descendant_size = 0 ancestor_size = sum([mempool[tx]['size'] for tx in mempool]) ancestor_count = MAX_ANCESTORS ancestor_fees = sum([mempool[tx]['fee'] for tx in mempool]) descendants = [] ancestors = list(chain) for x in reversed(chain): # Check that getmempoolentry is consistent with getrawmempool entry = self.nodes[0].getmempoolentry(x) assert_equal(entry, mempool[x]) # Check that the descendant calculations are correct assert_equal(mempool[x]['descendantcount'], descendant_count) descendant_fees += mempool[x]['fee'] assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']) assert_equal(mempool[x]['fees']['base'], mempool[x]['fee']) assert_equal(mempool[x]['fees']['modified'], mempool[x]['modifiedfee']) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN) assert_equal(mempool[x]['fees']['descendant'], descendant_fees) descendant_size += mempool[x]['size'] assert_equal(mempool[x]['descendantsize'], descendant_size) descendant_count += 1 # Check that ancestor calculations are correct assert_equal(mempool[x]['ancestorcount'], ancestor_count) assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN) assert_equal(mempool[x]['ancestorsize'], ancestor_size) ancestor_size -= mempool[x]['size'] ancestor_fees -= mempool[x]['fee'] ancestor_count -= 1 # Check that parent/child list is correct assert_equal(mempool[x]['spentby'], descendants[-1:]) assert_equal(mempool[x]['depends'], ancestors[-2:-1]) # Check that getmempooldescendants is correct assert_equal(sorted(descendants), sorted(self.nodes[0].getmempooldescendants(x))) # Check getmempooldescendants verbose output is correct for descendant, dinfo in self.nodes[0].getmempooldescendants(x, True).items(): assert_equal(dinfo['depends'], [chain[chain.index(descendant)-1]]) if dinfo['descendantcount'] > 1: assert_equal(dinfo['spentby'], [chain[chain.index(descendant)+1]]) else: assert_equal(dinfo['spentby'], []) descendants.append(x) # Check that getmempoolancestors is correct ancestors.remove(x) assert_equal(sorted(ancestors), sorted(self.nodes[0].getmempoolancestors(x))) # Check that getmempoolancestors verbose output is correct for ancestor, ainfo in self.nodes[0].getmempoolancestors(x, True).items(): assert_equal(ainfo['spentby'], [chain[chain.index(ancestor)+1]]) if ainfo['ancestorcount'] > 1: assert_equal(ainfo['depends'], [chain[chain.index(ancestor)-1]]) else: assert_equal(ainfo['depends'], []) # Check that getmempoolancestors/getmempooldescendants correctly handle verbose=true v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True) assert_equal(len(v_ancestors), len(chain)-1) for x in v_ancestors.keys(): assert_equal(mempool[x], v_ancestors[x]) assert(chain[-1] not in v_ancestors.keys()) v_descendants = self.nodes[0].getmempooldescendants(chain[0], True) assert_equal(len(v_descendants), len(chain)-1) for x in v_descendants.keys(): assert_equal(mempool[x], v_descendants[x]) assert(chain[0] not in v_descendants.keys()) # Check that ancestor modified fees includes fee deltas from # prioritisetransaction self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=1000) mempool = self.nodes[0].getrawmempool(True) ancestor_fees = 0 for x in chain: ancestor_fees += mempool[x]['fee'] assert_equal(mempool[x]['fees']['ancestor'], ancestor_fees + Decimal('0.00001')) assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN + 1000) # Undo the prioritisetransaction for later tests self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=-1000) # Check that descendant modified fees includes fee deltas from # prioritisetransaction self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=1000) mempool = self.nodes[0].getrawmempool(True) descendant_fees = 0 for x in reversed(chain): descendant_fees += mempool[x]['fee'] assert_equal(mempool[x]['fees']['descendant'], descendant_fees + Decimal('0.00001')) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 1000) # Adding one more transaction on to the chain should fail. assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], txid, vout, value, fee, 1) # Check that prioritising a tx before it's added to the mempool works # First clear the mempool by mining a block. self.nodes[0].generate(1) sync_blocks(self.nodes) assert_equal(len(self.nodes[0].getrawmempool()), 0) # Prioritise a transaction that has been mined, then add it back to the # mempool by using invalidateblock. self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=2000) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # Keep node1's tip synced with node0 self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash()) # Now check that the transaction is in the mempool, with the right modified fee mempool = self.nodes[0].getrawmempool(True) descendant_fees = 0 for x in reversed(chain): descendant_fees += mempool[x]['fee'] if (x == chain[-1]): assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']+satoshi_round(0.00002)) assert_equal(mempool[x]['fees']['modified'], mempool[x]['fee']+satoshi_round(0.00002)) assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 2000) assert_equal(mempool[x]['fees']['descendant'], descendant_fees+satoshi_round(0.00002)) # TODO: check that node1's mempool is as expected # TODO: test ancestor size limits # Now test descendant chain limits txid = utxo[1]['txid'] value = utxo[1]['amount'] vout = utxo[1]['vout'] transaction_package = [] tx_children = [] # First create one parent tx with 10 children (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 10) parent_transaction = txid for i in range(10): transaction_package.append({'txid': txid, 'vout': i, 'amount': sent_value}) # Sign and send up to MAX_DESCENDANT transactions chained off the parent tx for i in range(MAX_DESCENDANTS - 1): utxo = transaction_package.pop(0) (txid, sent_value) = self.chain_transaction(self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) if utxo['txid'] is parent_transaction: tx_children.append(txid) for j in range(10): transaction_package.append({'txid': txid, 'vout': j, 'amount': sent_value}) mempool = self.nodes[0].getrawmempool(True) assert_equal(mempool[parent_transaction]['descendantcount'], MAX_DESCENDANTS) assert_equal(sorted(mempool[parent_transaction]['spentby']), sorted(tx_children)) for child in tx_children: assert_equal(mempool[child]['depends'], [parent_transaction]) # Sending one more chained transaction will fail utxo = transaction_package.pop(0) assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) # TODO: check that node1's mempool is as expected # TODO: test descendant size limits # Test reorg handling # First, the basics: self.nodes[0].generate(1) sync_blocks(self.nodes) self.nodes[1].invalidateblock(self.nodes[0].getbestblockhash()) self.nodes[1].reconsiderblock(self.nodes[0].getbestblockhash()) # Now test the case where node1 has a transaction T in its mempool that # depends on transactions A and B which are in a mined block, and the # block containing A and B is disconnected, AND B is not accepted back # into node1's mempool because its ancestor count is too high. # Create 8 transactions, like so: # Tx0 -> Tx1 (vout0) # \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7 # # Mine them in the next block, then generate a new tx8 that spends # Tx1 and Tx7, and add to node1's mempool, then disconnect the # last block. # Create tx0 with 2 outputs utxo = self.nodes[0].listunspent() txid = utxo[0]['txid'] value = utxo[0]['amount'] vout = utxo[0]['vout'] send_value = satoshi_round((value - fee)/2) inputs = [ {'txid' : txid, 'vout' : vout} ] outputs = {} for i in range(2): outputs[self.nodes[0].getnewaddress()] = send_value rawtx = self.nodes[0].createrawtransaction(inputs, outputs) signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) txid = self.nodes[0].sendrawtransaction(signedtx['hex']) tx0_id = txid value = send_value # Create tx1 tx1_id, _ = self.chain_transaction(self.nodes[0], tx0_id, 0, value, fee, 1) # Create tx2-7 vout = 1 txid = tx0_id for i in range(6): (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 1) vout = 0 value = sent_value # Mine these in a block self.nodes[0].generate(1) self.sync_all() # Now generate tx8, with a big fee inputs = [ {'txid' : tx1_id, 'vout': 0}, {'txid' : txid, 'vout': 0} ] outputs = { self.nodes[0].getnewaddress() : send_value + value - 4*fee } rawtx = self.nodes[0].createrawtransaction(inputs, outputs) signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) txid = self.nodes[0].sendrawtransaction(signedtx['hex']) sync_mempools(self.nodes) # Now try to disconnect the tip on each node... self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash()) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) sync_blocks(self.nodes)
def total_fees(*txids): total = 0 for txid in txids: total += self.nodes[0].calculate_fee_from_txid(txid) return satoshi_round(total)