1#!/usr/bin/env python3 2# Copyright (c) 2014-2018 The Bitcoin Core developers 3# Distributed under the MIT software license, see the accompanying 4# file COPYING or http://www.opensource.org/licenses/mit-license.php. 5"""Test descendant package tracking code.""" 6 7from decimal import Decimal 8 9from test_framework.messages import COIN 10from test_framework.test_framework import BitcoinTestFramework 11from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round, sync_blocks, sync_mempools 12 13MAX_ANCESTORS = 25 14MAX_DESCENDANTS = 25 15 16class MempoolPackagesTest(BitcoinTestFramework): 17 def set_test_params(self): 18 self.num_nodes = 2 19 self.extra_args = [["-maxorphantx=1000"], ["-maxorphantx=1000", "-limitancestorcount=5"]] 20 21 def skip_test_if_missing_module(self): 22 self.skip_if_no_wallet() 23 24 # Build a transaction that spends parent_txid:vout 25 # Return amount sent 26 def chain_transaction(self, node, parent_txid, vout, value, fee, num_outputs): 27 send_value = satoshi_round((value - fee)/num_outputs) 28 inputs = [ {'txid' : parent_txid, 'vout' : vout} ] 29 outputs = {} 30 for i in range(num_outputs): 31 outputs[node.getnewaddress()] = send_value 32 rawtx = node.createrawtransaction(inputs, outputs) 33 signedtx = node.signrawtransactionwithwallet(rawtx) 34 txid = node.sendrawtransaction(signedtx['hex']) 35 fulltx = node.getrawtransaction(txid, 1) 36 assert(len(fulltx['vout']) == num_outputs) # make sure we didn't generate a change output 37 return (txid, send_value) 38 39 def run_test(self): 40 # Mine some blocks and have them mature. 41 self.nodes[0].generate(101) 42 utxo = self.nodes[0].listunspent(10) 43 txid = utxo[0]['txid'] 44 vout = utxo[0]['vout'] 45 value = utxo[0]['amount'] 46 47 fee = Decimal("0.0001") 48 # MAX_ANCESTORS transactions off a confirmed tx should be fine 49 chain = [] 50 for i in range(MAX_ANCESTORS): 51 (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, 0, value, fee, 1) 52 value = sent_value 53 chain.append(txid) 54 55 # Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor 56 # count and fees should look correct 57 mempool = self.nodes[0].getrawmempool(True) 58 assert_equal(len(mempool), MAX_ANCESTORS) 59 descendant_count = 1 60 descendant_fees = 0 61 descendant_size = 0 62 63 ancestor_size = sum([mempool[tx]['size'] for tx in mempool]) 64 ancestor_count = MAX_ANCESTORS 65 ancestor_fees = sum([mempool[tx]['fee'] for tx in mempool]) 66 67 descendants = [] 68 ancestors = list(chain) 69 for x in reversed(chain): 70 # Check that getmempoolentry is consistent with getrawmempool 71 entry = self.nodes[0].getmempoolentry(x) 72 assert_equal(entry, mempool[x]) 73 74 # Check that the descendant calculations are correct 75 assert_equal(mempool[x]['descendantcount'], descendant_count) 76 descendant_fees += mempool[x]['fee'] 77 assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']) 78 assert_equal(mempool[x]['fees']['base'], mempool[x]['fee']) 79 assert_equal(mempool[x]['fees']['modified'], mempool[x]['modifiedfee']) 80 assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN) 81 assert_equal(mempool[x]['fees']['descendant'], descendant_fees) 82 descendant_size += mempool[x]['size'] 83 assert_equal(mempool[x]['descendantsize'], descendant_size) 84 descendant_count += 1 85 86 # Check that ancestor calculations are correct 87 assert_equal(mempool[x]['ancestorcount'], ancestor_count) 88 assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN) 89 assert_equal(mempool[x]['ancestorsize'], ancestor_size) 90 ancestor_size -= mempool[x]['size'] 91 ancestor_fees -= mempool[x]['fee'] 92 ancestor_count -= 1 93 94 # Check that parent/child list is correct 95 assert_equal(mempool[x]['spentby'], descendants[-1:]) 96 assert_equal(mempool[x]['depends'], ancestors[-2:-1]) 97 98 # Check that getmempooldescendants is correct 99 assert_equal(sorted(descendants), sorted(self.nodes[0].getmempooldescendants(x))) 100 101 # Check getmempooldescendants verbose output is correct 102 for descendant, dinfo in self.nodes[0].getmempooldescendants(x, True).items(): 103 assert_equal(dinfo['depends'], [chain[chain.index(descendant)-1]]) 104 if dinfo['descendantcount'] > 1: 105 assert_equal(dinfo['spentby'], [chain[chain.index(descendant)+1]]) 106 else: 107 assert_equal(dinfo['spentby'], []) 108 descendants.append(x) 109 110 # Check that getmempoolancestors is correct 111 ancestors.remove(x) 112 assert_equal(sorted(ancestors), sorted(self.nodes[0].getmempoolancestors(x))) 113 114 # Check that getmempoolancestors verbose output is correct 115 for ancestor, ainfo in self.nodes[0].getmempoolancestors(x, True).items(): 116 assert_equal(ainfo['spentby'], [chain[chain.index(ancestor)+1]]) 117 if ainfo['ancestorcount'] > 1: 118 assert_equal(ainfo['depends'], [chain[chain.index(ancestor)-1]]) 119 else: 120 assert_equal(ainfo['depends'], []) 121 122 123 # Check that getmempoolancestors/getmempooldescendants correctly handle verbose=true 124 v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True) 125 assert_equal(len(v_ancestors), len(chain)-1) 126 for x in v_ancestors.keys(): 127 assert_equal(mempool[x], v_ancestors[x]) 128 assert(chain[-1] not in v_ancestors.keys()) 129 130 v_descendants = self.nodes[0].getmempooldescendants(chain[0], True) 131 assert_equal(len(v_descendants), len(chain)-1) 132 for x in v_descendants.keys(): 133 assert_equal(mempool[x], v_descendants[x]) 134 assert(chain[0] not in v_descendants.keys()) 135 136 # Check that ancestor modified fees includes fee deltas from 137 # prioritisetransaction 138 self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=1000) 139 mempool = self.nodes[0].getrawmempool(True) 140 ancestor_fees = 0 141 for x in chain: 142 ancestor_fees += mempool[x]['fee'] 143 assert_equal(mempool[x]['fees']['ancestor'], ancestor_fees + Decimal('0.00001')) 144 assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN + 1000) 145 146 # Undo the prioritisetransaction for later tests 147 self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=-1000) 148 149 # Check that descendant modified fees includes fee deltas from 150 # prioritisetransaction 151 self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=1000) 152 mempool = self.nodes[0].getrawmempool(True) 153 154 descendant_fees = 0 155 for x in reversed(chain): 156 descendant_fees += mempool[x]['fee'] 157 assert_equal(mempool[x]['fees']['descendant'], descendant_fees + Decimal('0.00001')) 158 assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 1000) 159 160 # Adding one more transaction on to the chain should fail. 161 assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], txid, vout, value, fee, 1) 162 163 # Check that prioritising a tx before it's added to the mempool works 164 # First clear the mempool by mining a block. 165 self.nodes[0].generate(1) 166 sync_blocks(self.nodes) 167 assert_equal(len(self.nodes[0].getrawmempool()), 0) 168 # Prioritise a transaction that has been mined, then add it back to the 169 # mempool by using invalidateblock. 170 self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=2000) 171 self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) 172 # Keep node1's tip synced with node0 173 self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash()) 174 175 # Now check that the transaction is in the mempool, with the right modified fee 176 mempool = self.nodes[0].getrawmempool(True) 177 178 descendant_fees = 0 179 for x in reversed(chain): 180 descendant_fees += mempool[x]['fee'] 181 if (x == chain[-1]): 182 assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']+satoshi_round(0.00002)) 183 assert_equal(mempool[x]['fees']['modified'], mempool[x]['fee']+satoshi_round(0.00002)) 184 assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 2000) 185 assert_equal(mempool[x]['fees']['descendant'], descendant_fees+satoshi_round(0.00002)) 186 187 # TODO: check that node1's mempool is as expected 188 189 # TODO: test ancestor size limits 190 191 # Now test descendant chain limits 192 txid = utxo[1]['txid'] 193 value = utxo[1]['amount'] 194 vout = utxo[1]['vout'] 195 196 transaction_package = [] 197 tx_children = [] 198 # First create one parent tx with 10 children 199 (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 10) 200 parent_transaction = txid 201 for i in range(10): 202 transaction_package.append({'txid': txid, 'vout': i, 'amount': sent_value}) 203 204 # Sign and send up to MAX_DESCENDANT transactions chained off the parent tx 205 for i in range(MAX_DESCENDANTS - 1): 206 utxo = transaction_package.pop(0) 207 (txid, sent_value) = self.chain_transaction(self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) 208 if utxo['txid'] is parent_transaction: 209 tx_children.append(txid) 210 for j in range(10): 211 transaction_package.append({'txid': txid, 'vout': j, 'amount': sent_value}) 212 213 mempool = self.nodes[0].getrawmempool(True) 214 assert_equal(mempool[parent_transaction]['descendantcount'], MAX_DESCENDANTS) 215 assert_equal(sorted(mempool[parent_transaction]['spentby']), sorted(tx_children)) 216 217 for child in tx_children: 218 assert_equal(mempool[child]['depends'], [parent_transaction]) 219 220 # Sending one more chained transaction will fail 221 utxo = transaction_package.pop(0) 222 assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) 223 224 # TODO: check that node1's mempool is as expected 225 226 # TODO: test descendant size limits 227 228 # Test reorg handling 229 # First, the basics: 230 self.nodes[0].generate(1) 231 sync_blocks(self.nodes) 232 self.nodes[1].invalidateblock(self.nodes[0].getbestblockhash()) 233 self.nodes[1].reconsiderblock(self.nodes[0].getbestblockhash()) 234 235 # Now test the case where node1 has a transaction T in its mempool that 236 # depends on transactions A and B which are in a mined block, and the 237 # block containing A and B is disconnected, AND B is not accepted back 238 # into node1's mempool because its ancestor count is too high. 239 240 # Create 8 transactions, like so: 241 # Tx0 -> Tx1 (vout0) 242 # \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7 243 # 244 # Mine them in the next block, then generate a new tx8 that spends 245 # Tx1 and Tx7, and add to node1's mempool, then disconnect the 246 # last block. 247 248 # Create tx0 with 2 outputs 249 utxo = self.nodes[0].listunspent() 250 txid = utxo[0]['txid'] 251 value = utxo[0]['amount'] 252 vout = utxo[0]['vout'] 253 254 send_value = satoshi_round((value - fee)/2) 255 inputs = [ {'txid' : txid, 'vout' : vout} ] 256 outputs = {} 257 for i in range(2): 258 outputs[self.nodes[0].getnewaddress()] = send_value 259 rawtx = self.nodes[0].createrawtransaction(inputs, outputs) 260 signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) 261 txid = self.nodes[0].sendrawtransaction(signedtx['hex']) 262 tx0_id = txid 263 value = send_value 264 265 # Create tx1 266 tx1_id, _ = self.chain_transaction(self.nodes[0], tx0_id, 0, value, fee, 1) 267 268 # Create tx2-7 269 vout = 1 270 txid = tx0_id 271 for i in range(6): 272 (txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 1) 273 vout = 0 274 value = sent_value 275 276 # Mine these in a block 277 self.nodes[0].generate(1) 278 self.sync_all() 279 280 # Now generate tx8, with a big fee 281 inputs = [ {'txid' : tx1_id, 'vout': 0}, {'txid' : txid, 'vout': 0} ] 282 outputs = { self.nodes[0].getnewaddress() : send_value + value - 4*fee } 283 rawtx = self.nodes[0].createrawtransaction(inputs, outputs) 284 signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) 285 txid = self.nodes[0].sendrawtransaction(signedtx['hex']) 286 sync_mempools(self.nodes) 287 288 # Now try to disconnect the tip on each node... 289 self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash()) 290 self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) 291 sync_blocks(self.nodes) 292 293if __name__ == '__main__': 294 MempoolPackagesTest().main() 295