Skip to content

Commit 82d2aef

Browse files
committed
Merge pull request #6715
60de0d5 Fix mempool package tracking edge case (Suhas Daftuar) 598b25d Add test showing bug in mempool packages (Suhas Daftuar)
2 parents 5b77244 + 60de0d5 commit 82d2aef

File tree

3 files changed

+121
-27
lines changed

3 files changed

+121
-27
lines changed

qa/rpc-tests/mempool_packages.py

+82-11
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,24 @@ class MempoolPackagesTest(BitcoinTestFramework):
1515

1616
def setup_network(self):
1717
self.nodes = []
18-
self.nodes.append(start_node(0, self.options.tmpdir, ["-maxorphantx=1000", "-relaypriority=0"]))
18+
self.nodes.append(start_node(0, self.options.tmpdir, ["-maxorphantx=1000", "-relaypriority=0", "-debug"]))
19+
self.nodes.append(start_node(1, self.options.tmpdir, ["-maxorphantx=1000", "-relaypriority=0", "-limitancestorcount=5", "-debug"]))
20+
connect_nodes(self.nodes[0], 1)
1921
self.is_network_split = False
2022
self.sync_all()
2123

2224
# Build a transaction that spends parent_txid:vout
2325
# Return amount sent
24-
def chain_transaction(self, parent_txid, vout, value, fee, num_outputs):
26+
def chain_transaction(self, node, parent_txid, vout, value, fee, num_outputs):
2527
send_value = satoshi_round((value - fee)/num_outputs)
2628
inputs = [ {'txid' : parent_txid, 'vout' : vout} ]
2729
outputs = {}
2830
for i in xrange(num_outputs):
29-
outputs[self.nodes[0].getnewaddress()] = send_value
30-
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
31-
signedtx = self.nodes[0].signrawtransaction(rawtx)
32-
txid = self.nodes[0].sendrawtransaction(signedtx['hex'])
33-
fulltx = self.nodes[0].getrawtransaction(txid, 1)
31+
outputs[node.getnewaddress()] = send_value
32+
rawtx = node.createrawtransaction(inputs, outputs)
33+
signedtx = node.signrawtransaction(rawtx)
34+
txid = node.sendrawtransaction(signedtx['hex'])
35+
fulltx = node.getrawtransaction(txid, 1)
3436
assert(len(fulltx['vout']) == num_outputs) # make sure we didn't generate a change output
3537
return (txid, send_value)
3638

@@ -46,7 +48,7 @@ def run_test(self):
4648
# 100 transactions off a confirmed tx should be fine
4749
chain = []
4850
for i in xrange(100):
49-
(txid, sent_value) = self.chain_transaction(txid, 0, value, fee, 1)
51+
(txid, sent_value) = self.chain_transaction(self.nodes[0], txid, 0, value, fee, 1)
5052
value = sent_value
5153
chain.append(txid)
5254

@@ -69,10 +71,12 @@ def run_test(self):
6971

7072
# Adding one more transaction on to the chain should fail.
7173
try:
72-
self.chain_transaction(txid, vout, value, fee, 1)
74+
self.chain_transaction(self.nodes[0], txid, vout, value, fee, 1)
7375
except JSONRPCException as e:
7476
print "too-long-ancestor-chain successfully rejected"
7577

78+
# TODO: check that node1's mempool is as expected
79+
7680
# TODO: test ancestor size limits
7781

7882
# Now test descendant chain limits
@@ -82,15 +86,15 @@ def run_test(self):
8286

8387
transaction_package = []
8488
# First create one parent tx with 10 children
85-
(txid, sent_value) = self.chain_transaction(txid, vout, value, fee, 10)
89+
(txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 10)
8690
parent_transaction = txid
8791
for i in xrange(10):
8892
transaction_package.append({'txid': txid, 'vout': i, 'amount': sent_value})
8993

9094
for i in xrange(1000):
9195
utxo = transaction_package.pop(0)
9296
try:
93-
(txid, sent_value) = self.chain_transaction(utxo['txid'], utxo['vout'], utxo['amount'], fee, 10)
97+
(txid, sent_value) = self.chain_transaction(self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10)
9498
for j in xrange(10):
9599
transaction_package.append({'txid': txid, 'vout': j, 'amount': sent_value})
96100
if i == 998:
@@ -101,7 +105,74 @@ def run_test(self):
101105
assert_equal(i, 999)
102106
print "tx that would create too large descendant package successfully rejected"
103107

108+
# TODO: check that node1's mempool is as expected
109+
104110
# TODO: test descendant size limits
105111

112+
# Test reorg handling
113+
# First, the basics:
114+
self.nodes[0].generate(1)
115+
sync_blocks(self.nodes)
116+
self.nodes[1].invalidateblock(self.nodes[0].getbestblockhash())
117+
self.nodes[1].reconsiderblock(self.nodes[0].getbestblockhash())
118+
119+
# Now test the case where node1 has a transaction T in its mempool that
120+
# depends on transactions A and B which are in a mined block, and the
121+
# block containing A and B is disconnected, AND B is not accepted back
122+
# into node1's mempool because its ancestor count is too high.
123+
124+
# Create 8 transactions, like so:
125+
# Tx0 -> Tx1 (vout0)
126+
# \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7
127+
#
128+
# Mine them in the next block, then generate a new tx8 that spends
129+
# Tx1 and Tx7, and add to node1's mempool, then disconnect the
130+
# last block.
131+
132+
# Create tx0 with 2 outputs
133+
utxo = self.nodes[0].listunspent()
134+
txid = utxo[0]['txid']
135+
value = utxo[0]['amount']
136+
vout = utxo[0]['vout']
137+
138+
send_value = satoshi_round((value - fee)/2)
139+
inputs = [ {'txid' : txid, 'vout' : vout} ]
140+
outputs = {}
141+
for i in xrange(2):
142+
outputs[self.nodes[0].getnewaddress()] = send_value
143+
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
144+
signedtx = self.nodes[0].signrawtransaction(rawtx)
145+
txid = self.nodes[0].sendrawtransaction(signedtx['hex'])
146+
tx0_id = txid
147+
value = send_value
148+
149+
# Create tx1
150+
(tx1_id, tx1_value) = self.chain_transaction(self.nodes[0], tx0_id, 0, value, fee, 1)
151+
152+
# Create tx2-7
153+
vout = 1
154+
txid = tx0_id
155+
for i in xrange(6):
156+
(txid, sent_value) = self.chain_transaction(self.nodes[0], txid, vout, value, fee, 1)
157+
vout = 0
158+
value = sent_value
159+
160+
# Mine these in a block
161+
self.nodes[0].generate(1)
162+
self.sync_all()
163+
164+
# Now generate tx8, with a big fee
165+
inputs = [ {'txid' : tx1_id, 'vout': 0}, {'txid' : txid, 'vout': 0} ]
166+
outputs = { self.nodes[0].getnewaddress() : send_value + value - 4*fee }
167+
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
168+
signedtx = self.nodes[0].signrawtransaction(rawtx)
169+
txid = self.nodes[0].sendrawtransaction(signedtx['hex'])
170+
sync_mempools(self.nodes)
171+
172+
# Now try to disconnect the tip on each node...
173+
self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())
174+
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
175+
sync_blocks(self.nodes)
176+
106177
if __name__ == '__main__':
107178
MempoolPackagesTest().main()

src/txmempool.cpp

+36-15
Original file line numberDiff line numberDiff line change
@@ -159,26 +159,30 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes
159159
}
160160
}
161161

162-
bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString)
162+
bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString, bool fSearchForParents /* = true */)
163163
{
164164
setEntries parentHashes;
165165
const CTransaction &tx = entry.GetTx();
166166

167-
// Get parents of this transaction that are in the mempool
168-
// Entry may or may not already be in the mempool, and GetMemPoolParents()
169-
// is only valid for entries in the mempool, so we iterate mapTx to find
170-
// parents.
171-
// TODO: optimize this so that we only check limits and walk
172-
// tx.vin when called on entries not already in the mempool.
173-
for (unsigned int i = 0; i < tx.vin.size(); i++) {
174-
txiter piter = mapTx.find(tx.vin[i].prevout.hash);
175-
if (piter != mapTx.end()) {
176-
parentHashes.insert(piter);
177-
if (parentHashes.size() + 1 > limitAncestorCount) {
178-
errString = strprintf("too many unconfirmed parents [limit: %u]", limitAncestorCount);
179-
return false;
167+
if (fSearchForParents) {
168+
// Get parents of this transaction that are in the mempool
169+
// GetMemPoolParents() is only valid for entries in the mempool, so we
170+
// iterate mapTx to find parents.
171+
for (unsigned int i = 0; i < tx.vin.size(); i++) {
172+
txiter piter = mapTx.find(tx.vin[i].prevout.hash);
173+
if (piter != mapTx.end()) {
174+
parentHashes.insert(piter);
175+
if (parentHashes.size() + 1 > limitAncestorCount) {
176+
errString = strprintf("too many unconfirmed parents [limit: %u]", limitAncestorCount);
177+
return false;
178+
}
180179
}
181180
}
181+
} else {
182+
// If we're not searching for parents, we require this to be an
183+
// entry in the mempool already.
184+
txiter it = mapTx.iterator_to(entry);
185+
parentHashes = GetMemPoolParents(it);
182186
}
183187

184188
size_t totalSizeWithAncestors = entry.GetTxSize();
@@ -249,7 +253,24 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove)
249253
setEntries setAncestors;
250254
const CTxMemPoolEntry &entry = *removeIt;
251255
std::string dummy;
252-
CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy);
256+
// Since this is a tx that is already in the mempool, we can call CMPA
257+
// with fSearchForParents = false. If the mempool is in a consistent
258+
// state, then using true or false should both be correct, though false
259+
// should be a bit faster.
260+
// However, if we happen to be in the middle of processing a reorg, then
261+
// the mempool can be in an inconsistent state. In this case, the set
262+
// of ancestors reachable via mapLinks will be the same as the set of
263+
// ancestors whose packages include this transaction, because when we
264+
// add a new transaction to the mempool in addUnchecked(), we assume it
265+
// has no children, and in the case of a reorg where that assumption is
266+
// false, the in-mempool children aren't linked to the in-block tx's
267+
// until UpdateTransactionsFromBlock() is called.
268+
// So if we're being called during a reorg, ie before
269+
// UpdateTransactionsFromBlock() has been called, then mapLinks[] will
270+
// differ from the set of mempool parents we'd calculate by searching,
271+
// and it's important that we use the mapLinks[] notion of ancestor
272+
// transactions as the set of things to update for removal.
273+
CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy, false);
253274
// Note that UpdateAncestorsOf severs the child links that point to
254275
// removeIt in the entries for the parents of removeIt. This is
255276
// fine since we don't need to use the mempool children of any entries

src/txmempool.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,10 @@ class CTxMemPool
392392
* limitDescendantCount = max number of descendants any ancestor can have
393393
* limitDescendantSize = max size of descendants any ancestor can have
394394
* errString = populated with error reason if any limits are hit
395+
* fSearchForParents = whether to search a tx's vin for in-mempool parents, or
396+
* look up parents from mapLinks. Must be true for entries not in the mempool
395397
*/
396-
bool CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString);
398+
bool CalculateMemPoolAncestors(const CTxMemPoolEntry &entry, setEntries &setAncestors, uint64_t limitAncestorCount, uint64_t limitAncestorSize, uint64_t limitDescendantCount, uint64_t limitDescendantSize, std::string &errString, bool fSearchForParents = true);
397399

398400
unsigned long size()
399401
{

0 commit comments

Comments
 (0)