Skip to content

Commit 2ef5ffa

Browse files
committed
Merge pull request #6915
2d8860e Fix removeForReorg to use MedianTimePast (Suhas Daftuar) b7fa4aa Don't call removeForReorg if DisconnectTip fails (Suhas Daftuar) 7e49f5f Track coinbase spends in CTxMemPoolEntry (Suhas Daftuar) bb8ea1f removeForReorg calls once-per-disconnect-> once-per-reorg (Matt Corallo) 474b84a Make indentation in ActivateBestChainStep readable (Matt Corallo) b0a064c Fix comment in removeForReorg (Matt Corallo) 9b060e5 Fix removal of time-locked transactions during reorg (Matt Corallo) 0c9959a Add failing test checking timelocked-txn removal during reorg (Matt Corallo)
2 parents 9afbd96 + 2d8860e commit 2ef5ffa

File tree

9 files changed

+116
-77
lines changed

9 files changed

+116
-77
lines changed

qa/pull-tester/rpc-tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
'rawtransactions.py',
8383
'rest.py',
8484
'mempool_spendcoinbase.py',
85-
'mempool_coinbase_spends.py',
85+
'mempool_reorg.py',
8686
'httpbasics.py',
8787
'multi_rpc.py',
8888
'zapwallettxes.py',

qa/rpc-tests/mempool_coinbase_spends.py renamed to qa/rpc-tests/mempool_reorg.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,32 +52,46 @@ def run_test(self):
5252
# 3. Indirect (coinbase and child both in chain) : spend_103 and spend_103_1
5353
# Use invalidatblock to make all of the above coinbase spends invalid (immature coinbase),
5454
# and make sure the mempool code behaves correctly.
55-
b = [ self.nodes[0].getblockhash(n) for n in range(102, 105) ]
55+
b = [ self.nodes[0].getblockhash(n) for n in range(101, 105) ]
5656
coinbase_txids = [ self.nodes[0].getblock(h)['tx'][0] for h in b ]
57-
spend_101_raw = self.create_tx(coinbase_txids[0], node1_address, 50)
58-
spend_102_raw = self.create_tx(coinbase_txids[1], node0_address, 50)
59-
spend_103_raw = self.create_tx(coinbase_txids[2], node0_address, 50)
57+
spend_101_raw = self.create_tx(coinbase_txids[1], node1_address, 50)
58+
spend_102_raw = self.create_tx(coinbase_txids[2], node0_address, 50)
59+
spend_103_raw = self.create_tx(coinbase_txids[3], node0_address, 50)
60+
61+
# Create a block-height-locked transaction which will be invalid after reorg
62+
timelock_tx = self.nodes[0].createrawtransaction([{"txid": coinbase_txids[0], "vout": 0}], {node0_address: 50})
63+
# Set the time lock
64+
timelock_tx = timelock_tx.replace("ffffffff", "11111111", 1)
65+
timelock_tx = timelock_tx[:-8] + hex(self.nodes[0].getblockcount() + 2)[2:] + "000000"
66+
timelock_tx = self.nodes[0].signrawtransaction(timelock_tx)["hex"]
67+
assert_raises(JSONRPCException, self.nodes[0].sendrawtransaction, timelock_tx)
6068

6169
# Broadcast and mine spend_102 and 103:
6270
spend_102_id = self.nodes[0].sendrawtransaction(spend_102_raw)
6371
spend_103_id = self.nodes[0].sendrawtransaction(spend_103_raw)
6472
self.nodes[0].generate(1)
73+
assert_raises(JSONRPCException, self.nodes[0].sendrawtransaction, timelock_tx)
6574

6675
# Create 102_1 and 103_1:
6776
spend_102_1_raw = self.create_tx(spend_102_id, node1_address, 50)
6877
spend_103_1_raw = self.create_tx(spend_103_id, node1_address, 50)
6978

7079
# Broadcast and mine 103_1:
7180
spend_103_1_id = self.nodes[0].sendrawtransaction(spend_103_1_raw)
72-
self.nodes[0].generate(1)
81+
last_block = self.nodes[0].generate(1)
82+
timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx)
7383

7484
# ... now put spend_101 and spend_102_1 in memory pools:
7585
spend_101_id = self.nodes[0].sendrawtransaction(spend_101_raw)
7686
spend_102_1_id = self.nodes[0].sendrawtransaction(spend_102_1_raw)
7787

7888
self.sync_all()
7989

80-
assert_equal(set(self.nodes[0].getrawmempool()), set([ spend_101_id, spend_102_1_id ]))
90+
assert_equal(set(self.nodes[0].getrawmempool()), set([ spend_101_id, spend_102_1_id, timelock_tx_id ]))
91+
92+
for node in self.nodes:
93+
node.invalidateblock(last_block[0])
94+
assert_equal(set(self.nodes[0].getrawmempool()), set([ spend_101_id, spend_102_1_id, spend_103_1_id ]))
8195

8296
# Use invalidateblock to re-org back and make all those coinbase spends
8397
# immature/invalid:

src/main.cpp

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,18 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
953953
CAmount inChainInputValue;
954954
double dPriority = view.GetPriority(tx, chainActive.Height(), inChainInputValue);
955955

956-
CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), pool.HasNoInputsOf(tx), inChainInputValue);
956+
// Keep track of transactions that spend a coinbase, which we re-scan
957+
// during reorgs to ensure COINBASE_MATURITY is still met.
958+
bool fSpendsCoinbase = false;
959+
BOOST_FOREACH(const CTxIn &txin, tx.vin) {
960+
const CCoins *coins = view.AccessCoins(txin.prevout.hash);
961+
if (coins->IsCoinBase()) {
962+
fSpendsCoinbase = true;
963+
break;
964+
}
965+
}
966+
967+
CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), pool.HasNoInputsOf(tx), inChainInputValue, fSpendsCoinbase);
957968
unsigned int nSize = entry.GetTxSize();
958969

959970
// Don't accept it if it can't get into a block
@@ -2310,12 +2321,11 @@ void static UpdateTip(CBlockIndex *pindexNew) {
23102321
}
23112322
}
23122323

2313-
/** Disconnect chainActive's tip. You want to manually re-limit mempool size after this */
2324+
/** Disconnect chainActive's tip. You probably want to call mempool.removeForReorg and manually re-limit mempool size after this, with cs_main held. */
23142325
bool static DisconnectTip(CValidationState& state, const Consensus::Params& consensusParams)
23152326
{
23162327
CBlockIndex *pindexDelete = chainActive.Tip();
23172328
assert(pindexDelete);
2318-
mempool.check(pcoinsTip);
23192329
// Read block from disk.
23202330
CBlock block;
23212331
if (!ReadBlockFromDisk(block, pindexDelete, consensusParams))
@@ -2350,8 +2360,6 @@ bool static DisconnectTip(CValidationState& state, const Consensus::Params& cons
23502360
// UpdateTransactionsFromBlock finds descendants of any transactions in this
23512361
// block that were added back and cleans up the mempool state.
23522362
mempool.UpdateTransactionsFromBlock(vHashUpdate);
2353-
mempool.removeCoinbaseSpends(pcoinsTip, pindexDelete->nHeight);
2354-
mempool.check(pcoinsTip);
23552363
// Update chainActive and related variables.
23562364
UpdateTip(pindexDelete->pprev);
23572365
// Let wallets know transactions went from 1-confirmed to
@@ -2375,7 +2383,6 @@ static int64_t nTimePostConnect = 0;
23752383
bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const CBlock* pblock)
23762384
{
23772385
assert(pindexNew->pprev == chainActive.Tip());
2378-
mempool.check(pcoinsTip);
23792386
// Read block from disk.
23802387
int64_t nTime1 = GetTimeMicros();
23812388
CBlock block;
@@ -2412,7 +2419,6 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams,
24122419
// Remove conflicting transactions from the mempool.
24132420
list<CTransaction> txConflicted;
24142421
mempool.removeForBlock(pblock->vtx, pindexNew->nHeight, txConflicted, !IsInitialBlockDownload());
2415-
mempool.check(pcoinsTip);
24162422
// Update chainActive & related variables.
24172423
UpdateTip(pindexNew);
24182424
// Tell wallet about transactions that went from mempool
@@ -2525,46 +2531,49 @@ static bool ActivateBestChainStep(CValidationState& state, const CChainParams& c
25252531
bool fContinue = true;
25262532
int nHeight = pindexFork ? pindexFork->nHeight : -1;
25272533
while (fContinue && nHeight != pindexMostWork->nHeight) {
2528-
// Don't iterate the entire list of potential improvements toward the best tip, as we likely only need
2529-
// a few blocks along the way.
2530-
int nTargetHeight = std::min(nHeight + 32, pindexMostWork->nHeight);
2531-
vpindexToConnect.clear();
2532-
vpindexToConnect.reserve(nTargetHeight - nHeight);
2533-
CBlockIndex *pindexIter = pindexMostWork->GetAncestor(nTargetHeight);
2534-
while (pindexIter && pindexIter->nHeight != nHeight) {
2535-
vpindexToConnect.push_back(pindexIter);
2536-
pindexIter = pindexIter->pprev;
2537-
}
2538-
nHeight = nTargetHeight;
2539-
2540-
// Connect new blocks.
2541-
BOOST_REVERSE_FOREACH(CBlockIndex *pindexConnect, vpindexToConnect) {
2542-
if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : NULL)) {
2543-
if (state.IsInvalid()) {
2544-
// The block violates a consensus rule.
2545-
if (!state.CorruptionPossible())
2546-
InvalidChainFound(vpindexToConnect.back());
2547-
state = CValidationState();
2548-
fInvalidFound = true;
2549-
fContinue = false;
2550-
break;
2534+
// Don't iterate the entire list of potential improvements toward the best tip, as we likely only need
2535+
// a few blocks along the way.
2536+
int nTargetHeight = std::min(nHeight + 32, pindexMostWork->nHeight);
2537+
vpindexToConnect.clear();
2538+
vpindexToConnect.reserve(nTargetHeight - nHeight);
2539+
CBlockIndex *pindexIter = pindexMostWork->GetAncestor(nTargetHeight);
2540+
while (pindexIter && pindexIter->nHeight != nHeight) {
2541+
vpindexToConnect.push_back(pindexIter);
2542+
pindexIter = pindexIter->pprev;
2543+
}
2544+
nHeight = nTargetHeight;
2545+
2546+
// Connect new blocks.
2547+
BOOST_REVERSE_FOREACH(CBlockIndex *pindexConnect, vpindexToConnect) {
2548+
if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : NULL)) {
2549+
if (state.IsInvalid()) {
2550+
// The block violates a consensus rule.
2551+
if (!state.CorruptionPossible())
2552+
InvalidChainFound(vpindexToConnect.back());
2553+
state = CValidationState();
2554+
fInvalidFound = true;
2555+
fContinue = false;
2556+
break;
2557+
} else {
2558+
// A system error occurred (disk space, database error, ...).
2559+
return false;
2560+
}
25512561
} else {
2552-
// A system error occurred (disk space, database error, ...).
2553-
return false;
2554-
}
2555-
} else {
2556-
PruneBlockIndexCandidates();
2557-
if (!pindexOldTip || chainActive.Tip()->nChainWork > pindexOldTip->nChainWork) {
2558-
// We're in a better position than we were. Return temporarily to release the lock.
2559-
fContinue = false;
2560-
break;
2562+
PruneBlockIndexCandidates();
2563+
if (!pindexOldTip || chainActive.Tip()->nChainWork > pindexOldTip->nChainWork) {
2564+
// We're in a better position than we were. Return temporarily to release the lock.
2565+
fContinue = false;
2566+
break;
2567+
}
25612568
}
25622569
}
25632570
}
2564-
}
25652571

2566-
if (fBlocksDisconnected)
2572+
if (fBlocksDisconnected) {
2573+
mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS);
25672574
mempool.TrimToSize(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
2575+
}
2576+
mempool.check(pcoinsTip);
25682577

25692578
// Callbacks/notifications for a new best chain.
25702579
if (fInvalidFound)
@@ -2672,6 +2681,7 @@ bool InvalidateBlock(CValidationState& state, const Consensus::Params& consensus
26722681
// ActivateBestChain considers blocks already in chainActive
26732682
// unconditionally valid already, so force disconnect away from it.
26742683
if (!DisconnectTip(state, consensusParams)) {
2684+
mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS);
26752685
return false;
26762686
}
26772687
}
@@ -2689,6 +2699,7 @@ bool InvalidateBlock(CValidationState& state, const Consensus::Params& consensus
26892699
}
26902700

26912701
InvalidChainFound(pindex);
2702+
mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS);
26922703
return true;
26932704
}
26942705

src/main.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ bool InvalidateBlock(CValidationState& state, const Consensus::Params& consensus
467467
/** Remove invalidity status from a block and its descendants. */
468468
bool ReconsiderBlock(CValidationState& state, CBlockIndex *pindex);
469469

470-
/** The currently-connected chain of blocks. */
470+
/** The currently-connected chain of blocks (protected by cs_main). */
471471
extern CChain chainActive;
472472

473473
/** Global variable that points to the active CCoinsView (protected by cs_main) */

src/test/miner_tests.cpp

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
119119
{
120120
tx.vout[0].nValue -= 1000000;
121121
hash = tx.GetHash();
122-
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx));
122+
bool spendsCoinbase = (i == 0) ? true : false; // only first tx spends coinbase
123+
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
123124
tx.vin[0].prevout.hash = hash;
124125
}
125126
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
@@ -139,7 +140,8 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
139140
{
140141
tx.vout[0].nValue -= 10000000;
141142
hash = tx.GetHash();
142-
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx));
143+
bool spendsCoinbase = (i == 0) ? true : false; // only first tx spends coinbase
144+
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(spendsCoinbase).FromTx(tx));
143145
tx.vin[0].prevout.hash = hash;
144146
}
145147
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
@@ -158,15 +160,15 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
158160
tx.vin[0].prevout.hash = txFirst[1]->GetHash();
159161
tx.vout[0].nValue = 4900000000LL;
160162
hash = tx.GetHash();
161-
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx));
163+
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
162164
tx.vin[0].prevout.hash = hash;
163165
tx.vin.resize(2);
164166
tx.vin[1].scriptSig = CScript() << OP_1;
165167
tx.vin[1].prevout.hash = txFirst[0]->GetHash();
166168
tx.vin[1].prevout.n = 0;
167169
tx.vout[0].nValue = 5900000000LL;
168170
hash = tx.GetHash();
169-
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx));
171+
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
170172
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
171173
delete pblocktemplate;
172174
mempool.clear();
@@ -177,7 +179,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
177179
tx.vin[0].scriptSig = CScript() << OP_0 << OP_1;
178180
tx.vout[0].nValue = 0;
179181
hash = tx.GetHash();
180-
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx));
182+
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
181183
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
182184
delete pblocktemplate;
183185
mempool.clear();
@@ -190,12 +192,12 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
190192
script = CScript() << OP_0;
191193
tx.vout[0].scriptPubKey = GetScriptForDestination(CScriptID(script));
192194
hash = tx.GetHash();
193-
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx));
195+
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
194196
tx.vin[0].prevout.hash = hash;
195197
tx.vin[0].scriptSig = CScript() << std::vector<unsigned char>(script.begin(), script.end());
196198
tx.vout[0].nValue -= 1000000;
197199
hash = tx.GetHash();
198-
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx));
200+
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
199201
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
200202
delete pblocktemplate;
201203
mempool.clear();
@@ -206,10 +208,10 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
206208
tx.vout[0].nValue = 4900000000LL;
207209
tx.vout[0].scriptPubKey = CScript() << OP_1;
208210
hash = tx.GetHash();
209-
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx));
211+
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
210212
tx.vout[0].scriptPubKey = CScript() << OP_2;
211213
hash = tx.GetHash();
212-
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx));
214+
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
213215
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));
214216
delete pblocktemplate;
215217
mempool.clear();
@@ -235,7 +237,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
235237
tx.vout[0].scriptPubKey = CScript() << OP_1;
236238
tx.nLockTime = chainActive.Tip()->nHeight+1;
237239
hash = tx.GetHash();
238-
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx));
240+
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
239241
BOOST_CHECK(!CheckFinalTx(tx, LOCKTIME_MEDIAN_TIME_PAST));
240242

241243
// time locked
@@ -249,7 +251,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
249251
tx2.vout[0].scriptPubKey = CScript() << OP_1;
250252
tx2.nLockTime = chainActive.Tip()->GetMedianTimePast()+1;
251253
hash = tx2.GetHash();
252-
mempool.addUnchecked(hash, entry.Time(GetTime()).FromTx(tx2));
254+
mempool.addUnchecked(hash, entry.Time(GetTime()).SpendsCoinbase(true).FromTx(tx2));
253255
BOOST_CHECK(!CheckFinalTx(tx2, LOCKTIME_MEDIAN_TIME_PAST));
254256

255257
BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey));

src/test/test_bitcoin.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(CMutableTransaction &tx, CTxMemPo
150150
CAmount inChainValue = hasNoDependencies ? txn.GetValueOut() : 0;
151151

152152
return CTxMemPoolEntry(txn, nFee, nTime, dPriority, nHeight,
153-
hasNoDependencies, inChainValue);
153+
hasNoDependencies, inChainValue, spendsCoinbase);
154154
}
155155

156156
void Shutdown(void* parg)

src/test/test_bitcoin.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,11 @@ struct TestMemPoolEntryHelper
6565
double dPriority;
6666
unsigned int nHeight;
6767
bool hadNoDependencies;
68+
bool spendsCoinbase;
6869

6970
TestMemPoolEntryHelper() :
7071
nFee(0), nTime(0), dPriority(0.0), nHeight(1),
71-
hadNoDependencies(false) { }
72+
hadNoDependencies(false), spendsCoinbase(false) { }
7273

7374
CTxMemPoolEntry FromTx(CMutableTransaction &tx, CTxMemPool *pool = NULL);
7475

@@ -78,5 +79,6 @@ struct TestMemPoolEntryHelper
7879
TestMemPoolEntryHelper &Priority(double _priority) { dPriority = _priority; return *this; }
7980
TestMemPoolEntryHelper &Height(unsigned int _height) { nHeight = _height; return *this; }
8081
TestMemPoolEntryHelper &HadNoDependencies(bool _hnd) { hadNoDependencies = _hnd; return *this; }
82+
TestMemPoolEntryHelper &SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; }
8183
};
8284
#endif

0 commit comments

Comments
 (0)