Skip to content

Commit 73fa5e6

Browse files
committed
Merge pull request #6932
1cf3dd8 Add unit test for UpdateCoins (Alex Morcos) 03c8282 Make CCoinsViewTest behave like CCoinsViewDB (Alex Morcos) 14470f9 ModifyNewCoins saves database lookups (Alex Morcos)
2 parents 03403d8 + 1cf3dd8 commit 73fa5e6

File tree

4 files changed

+170
-9
lines changed

4 files changed

+170
-9
lines changed

src/coins.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) {
117117
return CCoinsModifier(*this, ret.first, cachedCoinUsage);
118118
}
119119

120+
CCoinsModifier CCoinsViewCache::ModifyNewCoins(const uint256 &txid) {
121+
assert(!hasModifier);
122+
std::pair<CCoinsMap::iterator, bool> ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry()));
123+
ret.first->second.coins.Clear();
124+
ret.first->second.flags = CCoinsCacheEntry::FRESH;
125+
ret.first->second.flags |= CCoinsCacheEntry::DIRTY;
126+
return CCoinsModifier(*this, ret.first, 0);
127+
}
128+
120129
const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const {
121130
CCoinsMap::const_iterator it = FetchCoins(txid);
122131
if (it == cacheCoins.end()) {

src/coins.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,17 @@ class CCoinsViewCache : public CCoinsViewBacked
419419
*/
420420
CCoinsModifier ModifyCoins(const uint256 &txid);
421421

422+
/**
423+
* Return a modifiable reference to a CCoins. Assumes that no entry with the given
424+
* txid exists and creates a new one. This saves a database access in the case where
425+
* the coins were to be wiped out by FromTx anyway. This should not be called with
426+
* the 2 historical coinbase duplicate pairs because the new coins are marked fresh, and
427+
* in the event the duplicate coinbase was spent before a flush, the now pruned coins
428+
* would not properly overwrite the first coinbase of the pair. Simultaneous modifications
429+
* are not allowed.
430+
*/
431+
CCoinsModifier ModifyNewCoins(const uint256 &txid);
432+
422433
/**
423434
* Push the modifications applied to this cache to its base.
424435
* Failure to call this method before destruction will cause the changes to be forgotten.

src/main.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,10 +1310,17 @@ void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCach
13101310
undo.nVersion = coins->nVersion;
13111311
}
13121312
}
1313+
// add outputs
1314+
inputs.ModifyNewCoins(tx.GetHash())->FromTx(tx, nHeight);
1315+
}
1316+
else {
1317+
// add outputs for coinbase tx
1318+
// In this case call the full ModifyCoins which will do a database
1319+
// lookup to be sure the coins do not already exist otherwise we do not
1320+
// know whether to mark them fresh or not. We want the duplicate coinbases
1321+
// before BIP30 to still be properly overwritten.
1322+
inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight);
13131323
}
1314-
1315-
// add outputs
1316-
inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight);
13171324
}
13181325

13191326
void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, int nHeight)

src/test/coins_tests.cpp

Lines changed: 140 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include "random.h"
77
#include "uint256.h"
88
#include "test/test_bitcoin.h"
9+
#include "main.h"
10+
#include "consensus/validation.h"
911

1012
#include <vector>
1113
#include <map>
@@ -45,15 +47,18 @@ class CCoinsViewTest : public CCoinsView
4547
bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock)
4648
{
4749
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
48-
map_[it->first] = it->second.coins;
49-
if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) {
50-
// Randomly delete empty entries on write.
51-
map_.erase(it->first);
50+
if (it->second.flags & CCoinsCacheEntry::DIRTY) {
51+
// Same optimization used in CCoinsViewDB is to only write dirty entries.
52+
map_[it->first] = it->second.coins;
53+
if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) {
54+
// Randomly delete empty entries on write.
55+
map_.erase(it->first);
56+
}
5257
}
5358
mapCoins.erase(it++);
5459
}
55-
mapCoins.clear();
56-
hashBestBlock_ = hashBlock;
60+
if (!hashBlock.IsNull())
61+
hashBestBlock_ = hashBlock;
5762
return true;
5863
}
5964

@@ -197,4 +202,133 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
197202
BOOST_CHECK(missed_an_entry);
198203
}
199204

205+
// This test is similar to the previous test
206+
// except the emphasis is on testing the functionality of UpdateCoins
207+
// random txs are created and UpdateCoins is used to update the cache stack
208+
// In particular it is tested that spending a duplicate coinbase tx
209+
// has the expected effect (the other duplicate is overwitten at all cache levels)
210+
BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
211+
{
212+
bool spent_a_duplicate_coinbase = false;
213+
// A simple map to track what we expect the cache stack to represent.
214+
std::map<uint256, CCoins> result;
215+
216+
// The cache stack.
217+
CCoinsViewTest base; // A CCoinsViewTest at the bottom.
218+
std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
219+
stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache.
220+
221+
// Track the txids we've used and whether they have been spent or not
222+
std::map<uint256, CAmount> coinbaseids;
223+
std::set<uint256> alltxids;
224+
std::set<uint256> duplicateids;
225+
226+
for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {
227+
{
228+
CMutableTransaction tx;
229+
tx.vin.resize(1);
230+
tx.vout.resize(1);
231+
tx.vout[0].nValue = i; //Keep txs unique unless intended to duplicate
232+
unsigned int height = insecure_rand();
233+
234+
// 1/10 times create a coinbase
235+
if (insecure_rand() % 10 == 0 || coinbaseids.size() < 10) {
236+
// 1/100 times create a duplicate coinbase
237+
if (insecure_rand() % 10 == 0 && coinbaseids.size()) {
238+
std::map<uint256, CAmount>::iterator coinbaseIt = coinbaseids.lower_bound(GetRandHash());
239+
if (coinbaseIt == coinbaseids.end()) {
240+
coinbaseIt = coinbaseids.begin();
241+
}
242+
//Use same random value to have same hash and be a true duplicate
243+
tx.vout[0].nValue = coinbaseIt->second;
244+
assert(tx.GetHash() == coinbaseIt->first);
245+
duplicateids.insert(coinbaseIt->first);
246+
}
247+
else {
248+
coinbaseids[tx.GetHash()] = tx.vout[0].nValue;
249+
}
250+
assert(CTransaction(tx).IsCoinBase());
251+
}
252+
// 9/10 times create a regular tx
253+
else {
254+
uint256 prevouthash;
255+
// equally likely to spend coinbase or non coinbase
256+
std::set<uint256>::iterator txIt = alltxids.lower_bound(GetRandHash());
257+
if (txIt == alltxids.end()) {
258+
txIt = alltxids.begin();
259+
}
260+
prevouthash = *txIt;
261+
262+
// Construct the tx to spend the coins of prevouthash
263+
tx.vin[0].prevout.hash = prevouthash;
264+
tx.vin[0].prevout.n = 0;
265+
266+
// Update the expected result of prevouthash to know these coins are spent
267+
CCoins& oldcoins = result[prevouthash];
268+
oldcoins.Clear();
269+
270+
// It is of particular importance here that once we spend a coinbase tx hash
271+
// it is no longer available to be duplicated (or spent again)
272+
// BIP 34 in conjunction with enforcing BIP 30 (at least until BIP 34 was active)
273+
// results in the fact that no coinbases were duplicated after they were already spent
274+
alltxids.erase(prevouthash);
275+
coinbaseids.erase(prevouthash);
276+
277+
// The test is designed to ensure spending a duplicate coinbase will work properly
278+
// if that ever happens and not resurrect the previously overwritten coinbase
279+
if (duplicateids.count(prevouthash))
280+
spent_a_duplicate_coinbase = true;
281+
282+
assert(!CTransaction(tx).IsCoinBase());
283+
}
284+
// Track this tx to possibly spend later
285+
alltxids.insert(tx.GetHash());
286+
287+
// Update the expected result to know about the new output coins
288+
CCoins &coins = result[tx.GetHash()];
289+
coins.FromTx(tx, height);
290+
291+
CValidationState dummy;
292+
UpdateCoins(tx, dummy, *(stack.back()), height);
293+
}
294+
295+
// Once every 1000 iterations and at the end, verify the full cache.
296+
if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {
297+
for (std::map<uint256, CCoins>::iterator it = result.begin(); it != result.end(); it++) {
298+
const CCoins* coins = stack.back()->AccessCoins(it->first);
299+
if (coins) {
300+
BOOST_CHECK(*coins == it->second);
301+
} else {
302+
BOOST_CHECK(it->second.IsPruned());
303+
}
304+
}
305+
}
306+
307+
if (insecure_rand() % 100 == 0) {
308+
// Every 100 iterations, change the cache stack.
309+
if (stack.size() > 0 && insecure_rand() % 2 == 0) {
310+
stack.back()->Flush();
311+
delete stack.back();
312+
stack.pop_back();
313+
}
314+
if (stack.size() == 0 || (stack.size() < 4 && insecure_rand() % 2)) {
315+
CCoinsView* tip = &base;
316+
if (stack.size() > 0) {
317+
tip = stack.back();
318+
}
319+
stack.push_back(new CCoinsViewCacheTest(tip));
320+
}
321+
}
322+
}
323+
324+
// Clean up the stack.
325+
while (stack.size() > 0) {
326+
delete stack.back();
327+
stack.pop_back();
328+
}
329+
330+
// Verify coverage.
331+
BOOST_CHECK(spent_a_duplicate_coinbase);
332+
}
333+
200334
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)