Skip to content

Commit 1c62e84

Browse files
gavinandresenlaanwj
authored andcommitted
Keep mempool consistent during block-reorgs
This fixes a subtle bug involving block re-orgs and non-standard transactions. Start with a block containing a non-standard transaction, and one or more transactions spending it in the memory pool. Then re-org away from that block to another chain that does not contain the non-standard transaction. Result before this fix: the dependent transactions get stuck in the mempool without their parent, putting the mempool in an inconsistent state. Tested with a new unit test (adapted for 0.10). Rebased-From: ad9e86d Github-Pull: #5945
1 parent 149c1d8 commit 1c62e84

File tree

3 files changed

+115
-0
lines changed

3 files changed

+115
-0
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ BITCOIN_TESTS =\
5151
test/hash_tests.cpp \
5252
test/key_tests.cpp \
5353
test/main_tests.cpp \
54+
test/mempool_tests.cpp \
5455
test/miner_tests.cpp \
5556
test/mruset_tests.cpp \
5657
test/multisig_tests.cpp \

src/test/mempool_tests.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright (c) 2011-2014 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include "main.h"
6+
#include "txmempool.h"
7+
#include "util.h"
8+
9+
#include <boost/test/unit_test.hpp>
10+
#include <list>
11+
12+
BOOST_AUTO_TEST_SUITE(mempool_tests)
13+
14+
BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
15+
{
16+
// Test CTxMemPool::remove functionality
17+
18+
// Parent transaction with three children,
19+
// and three grand-children:
20+
CMutableTransaction txParent;
21+
txParent.vin.resize(1);
22+
txParent.vin[0].scriptSig = CScript() << OP_11;
23+
txParent.vout.resize(3);
24+
for (int i = 0; i < 3; i++)
25+
{
26+
txParent.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
27+
txParent.vout[i].nValue = 33000LL;
28+
}
29+
CMutableTransaction txChild[3];
30+
for (int i = 0; i < 3; i++)
31+
{
32+
txChild[i].vin.resize(1);
33+
txChild[i].vin[0].scriptSig = CScript() << OP_11;
34+
txChild[i].vin[0].prevout.hash = txParent.GetHash();
35+
txChild[i].vin[0].prevout.n = i;
36+
txChild[i].vout.resize(1);
37+
txChild[i].vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
38+
txChild[i].vout[0].nValue = 11000LL;
39+
}
40+
CMutableTransaction txGrandChild[3];
41+
for (int i = 0; i < 3; i++)
42+
{
43+
txGrandChild[i].vin.resize(1);
44+
txGrandChild[i].vin[0].scriptSig = CScript() << OP_11;
45+
txGrandChild[i].vin[0].prevout.hash = txChild[i].GetHash();
46+
txGrandChild[i].vin[0].prevout.n = 0;
47+
txGrandChild[i].vout.resize(1);
48+
txGrandChild[i].vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
49+
txGrandChild[i].vout[0].nValue = 11000LL;
50+
}
51+
52+
53+
CTxMemPool testPool(CFeeRate(0));
54+
std::list<CTransaction> removed;
55+
56+
// Nothing in pool, remove should do nothing:
57+
testPool.remove(txParent, removed, true);
58+
BOOST_CHECK_EQUAL(removed.size(), 0);
59+
60+
// Just the parent:
61+
testPool.addUnchecked(txParent.GetHash(), CTxMemPoolEntry(txParent, 0, 0, 0.0, 1));
62+
testPool.remove(txParent, removed, true);
63+
BOOST_CHECK_EQUAL(removed.size(), 1);
64+
removed.clear();
65+
66+
// Parent, children, grandchildren:
67+
testPool.addUnchecked(txParent.GetHash(), CTxMemPoolEntry(txParent, 0, 0, 0.0, 1));
68+
for (int i = 0; i < 3; i++)
69+
{
70+
testPool.addUnchecked(txChild[i].GetHash(), CTxMemPoolEntry(txChild[i], 0, 0, 0.0, 1));
71+
testPool.addUnchecked(txGrandChild[i].GetHash(), CTxMemPoolEntry(txGrandChild[i], 0, 0, 0.0, 1));
72+
}
73+
// Remove Child[0], GrandChild[0] should be removed:
74+
testPool.remove(txChild[0], removed, true);
75+
BOOST_CHECK_EQUAL(removed.size(), 2);
76+
removed.clear();
77+
// ... make sure grandchild and child are gone:
78+
testPool.remove(txGrandChild[0], removed, true);
79+
BOOST_CHECK_EQUAL(removed.size(), 0);
80+
testPool.remove(txChild[0], removed, true);
81+
BOOST_CHECK_EQUAL(removed.size(), 0);
82+
// Remove parent, all children/grandchildren should go:
83+
testPool.remove(txParent, removed, true);
84+
BOOST_CHECK_EQUAL(removed.size(), 5);
85+
BOOST_CHECK_EQUAL(testPool.size(), 0);
86+
removed.clear();
87+
88+
// Add children and grandchildren, but NOT the parent (simulate the parent being in a block)
89+
for (int i = 0; i < 3; i++)
90+
{
91+
testPool.addUnchecked(txChild[i].GetHash(), CTxMemPoolEntry(txChild[i], 0, 0, 0.0, 1));
92+
testPool.addUnchecked(txGrandChild[i].GetHash(), CTxMemPoolEntry(txGrandChild[i], 0, 0, 0.0, 1));
93+
}
94+
// Now remove the parent, as might happen if a block-re-org occurs but the parent cannot be
95+
// put into the mempool (maybe because it is non-standard):
96+
testPool.remove(txParent, removed, true);
97+
BOOST_CHECK_EQUAL(removed.size(), 6);
98+
BOOST_CHECK_EQUAL(testPool.size(), 0);
99+
removed.clear();
100+
}
101+
102+
BOOST_AUTO_TEST_SUITE_END()

src/txmempool.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,18 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& rem
444444
LOCK(cs);
445445
std::deque<uint256> txToRemove;
446446
txToRemove.push_back(origTx.GetHash());
447+
if (fRecursive && !mapTx.count(origTx.GetHash())) {
448+
// If recursively removing but origTx isn't in the mempool
449+
// be sure to remove any children that are in the pool. This can
450+
// happen during chain re-orgs if origTx isn't re-accepted into
451+
// the mempool for any reason.
452+
for (unsigned int i = 0; i < origTx.vout.size(); i++) {
453+
std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(COutPoint(origTx.GetHash(), i));
454+
if (it == mapNextTx.end())
455+
continue;
456+
txToRemove.push_back(it->second.ptx->GetHash());
457+
}
458+
}
447459
while (!txToRemove.empty())
448460
{
449461
uint256 hash = txToRemove.front();

0 commit comments

Comments
 (0)