5
5
#include " coins.h"
6
6
#include " script/standard.h"
7
7
#include " uint256.h"
8
+ #include " undo.h"
8
9
#include " utilstrencodings.h"
9
10
#include " test/test_bitcoin.h"
10
11
#include " test/test_random.h"
16
17
17
18
#include < boost/test/unit_test.hpp>
18
19
20
+ bool ApplyTxInUndo (const CTxInUndo& undo, CCoinsViewCache& view, const COutPoint& out);
21
+ void UpdateCoins (const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight);
22
+
19
23
namespace
20
24
{
21
25
class CCoinsViewTest : public CCoinsView
@@ -213,6 +217,22 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
213
217
BOOST_CHECK (missed_an_entry);
214
218
}
215
219
220
+ typedef std::tuple<CTransaction,CTxUndo,CCoins> TxData;
221
+ // Store of all necessary tx and undo data for next test
222
+ std::map<uint256, TxData> alltxs;
223
+
224
+ TxData &FindRandomFrom (const std::set<uint256> &txidset) {
225
+ assert (txidset.size ());
226
+ std::set<uint256>::iterator txIt = txidset.lower_bound (GetRandHash ());
227
+ if (txIt == txidset.end ()) {
228
+ txIt = txidset.begin ();
229
+ }
230
+ std::map<uint256, TxData>::iterator txdit = alltxs.find (*txIt);
231
+ assert (txdit != alltxs.end ());
232
+ return txdit->second ;
233
+ }
234
+
235
+
216
236
// This test is similar to the previous test
217
237
// except the emphasis is on testing the functionality of UpdateCoins
218
238
// random txs are created and UpdateCoins is used to update the cache stack
@@ -229,77 +249,139 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
229
249
std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
230
250
stack.push_back (new CCoinsViewCacheTest (&base)); // Start with one cache.
231
251
232
- // Track the txids we've used and whether they have been spent or not
233
- std::map <uint256, CAmount > coinbaseids;
234
- std::set<uint256> alltxids ;
252
+ // Track the txids we've used in various sets
253
+ std::set <uint256> coinbaseids;
254
+ std::set<uint256> disconnectedids ;
235
255
std::set<uint256> duplicateids;
256
+ std::set<uint256> utxoset;
236
257
237
258
for (unsigned int i = 0 ; i < NUM_SIMULATION_ITERATIONS; i++) {
238
- {
259
+ uint32_t randiter = insecure_rand ();
260
+
261
+ // 19/20 txs add a new transaction
262
+ if (randiter % 20 < 19 ) {
239
263
CMutableTransaction tx;
240
264
tx.vin .resize (1 );
241
265
tx.vout .resize (1 );
242
266
tx.vout [0 ].nValue = i; // Keep txs unique unless intended to duplicate
243
267
unsigned int height = insecure_rand ();
268
+ CCoins oldcoins;
244
269
245
- // 1/10 times create a coinbase
246
- if (insecure_rand () % 10 == 0 || coinbaseids.size () < 10 ) {
247
- // 1/100 times create a duplicate coinbase
270
+ // 2/20 times create a new coinbase
271
+ if (randiter % 20 < 2 || coinbaseids.size () < 10 ) {
272
+ // 1/10 of those times create a duplicate coinbase
248
273
if (insecure_rand () % 10 == 0 && coinbaseids.size ()) {
249
- std::map<uint256, CAmount>::iterator coinbaseIt = coinbaseids.lower_bound (GetRandHash ());
250
- if (coinbaseIt == coinbaseids.end ()) {
251
- coinbaseIt = coinbaseids.begin ();
252
- }
253
- // Use same random value to have same hash and be a true duplicate
254
- tx.vout [0 ].nValue = coinbaseIt->second ;
255
- assert (tx.GetHash () == coinbaseIt->first );
256
- duplicateids.insert (coinbaseIt->first );
274
+ TxData &txd = FindRandomFrom (coinbaseids);
275
+ // Reuse the exact same coinbase
276
+ tx = std::get<0 >(txd);
277
+ // shouldn't be available for reconnection if its been duplicated
278
+ disconnectedids.erase (tx.GetHash ());
279
+
280
+ duplicateids.insert (tx.GetHash ());
257
281
}
258
282
else {
259
- coinbaseids[ tx.GetHash ()] = tx. vout [ 0 ]. nValue ;
283
+ coinbaseids. insert ( tx.GetHash ()) ;
260
284
}
261
285
assert (CTransaction (tx).IsCoinBase ());
262
286
}
263
- // 9/10 times create a regular tx
287
+
288
+ // 17/20 times reconnect previous or add a regular tx
264
289
else {
290
+
265
291
uint256 prevouthash;
266
- // equally likely to spend coinbase or non coinbase
267
- std::set<uint256>::iterator txIt = alltxids.lower_bound (GetRandHash ());
268
- if (txIt == alltxids.end ()) {
269
- txIt = alltxids.begin ();
292
+ // 1/20 times reconnect a previously disconnected tx
293
+ if (randiter % 20 == 2 && disconnectedids.size ()) {
294
+ TxData &txd = FindRandomFrom (disconnectedids);
295
+ tx = std::get<0 >(txd);
296
+ prevouthash = tx.vin [0 ].prevout .hash ;
297
+ if (!CTransaction (tx).IsCoinBase () && !utxoset.count (prevouthash)) {
298
+ disconnectedids.erase (tx.GetHash ());
299
+ continue ;
300
+ }
301
+
302
+ // If this tx is already IN the UTXO, then it must be a coinbase, and it must be a duplicate
303
+ if (utxoset.count (tx.GetHash ())) {
304
+ assert (CTransaction (tx).IsCoinBase ());
305
+ assert (duplicateids.count (tx.GetHash ()));
306
+ }
307
+ disconnectedids.erase (tx.GetHash ());
270
308
}
271
- prevouthash = *txIt;
272
309
273
- // Construct the tx to spend the coins of prevouthash
274
- tx.vin [0 ].prevout .hash = prevouthash;
275
- tx.vin [0 ].prevout .n = 0 ;
310
+ // 16/20 times create a regular tx
311
+ else {
312
+ TxData &txd = FindRandomFrom (utxoset);
313
+ prevouthash = std::get<0 >(txd).GetHash ();
276
314
315
+ // Construct the tx to spend the coins of prevouthash
316
+ tx.vin [0 ].prevout .hash = prevouthash;
317
+ tx.vin [0 ].prevout .n = 0 ;
318
+ assert (!CTransaction (tx).IsCoinBase ());
319
+ }
320
+ // In this simple test coins only have two states, spent or unspent, save the unspent state to restore
321
+ oldcoins = result[prevouthash];
277
322
// Update the expected result of prevouthash to know these coins are spent
278
- CCoins& oldcoins = result[prevouthash];
279
- oldcoins.Clear ();
323
+ result[prevouthash].Clear ();
280
324
281
- // It is of particular importance here that once we spend a coinbase tx hash
282
- // it is no longer available to be duplicated (or spent again)
283
- // BIP 34 in conjunction with enforcing BIP 30 (at least until BIP 34 was active)
284
- // results in the fact that no coinbases were duplicated after they were already spent
285
- alltxids.erase (prevouthash);
286
- coinbaseids.erase (prevouthash);
325
+ utxoset.erase (prevouthash);
287
326
288
327
// The test is designed to ensure spending a duplicate coinbase will work properly
289
328
// if that ever happens and not resurrect the previously overwritten coinbase
290
329
if (duplicateids.count (prevouthash))
291
330
spent_a_duplicate_coinbase = true ;
292
331
293
- assert (!CTransaction (tx).IsCoinBase ());
294
332
}
295
- // Track this tx to possibly spend later
296
- alltxids.insert (tx.GetHash ());
297
-
298
333
// Update the expected result to know about the new output coins
299
- CCoins &coins = result[tx.GetHash ()];
300
- coins.FromTx (tx, height);
334
+ result[tx.GetHash ()].FromTx (tx, height);
335
+
336
+ // Call UpdateCoins on the top cache
337
+ CTxUndo undo;
338
+ UpdateCoins (tx, *(stack.back ()), undo, height);
301
339
302
- UpdateCoins (tx, *(stack.back ()), height);
340
+ // Update the utxo set for future spends
341
+ utxoset.insert (tx.GetHash ());
342
+
343
+ // Track this tx and undo info to use later
344
+ alltxs.insert (std::make_pair (tx.GetHash (),std::make_tuple (tx,undo,oldcoins)));
345
+ }
346
+
347
+ // 1/20 times undo a previous transaction
348
+ else if (utxoset.size ()) {
349
+ TxData &txd = FindRandomFrom (utxoset);
350
+
351
+ CTransaction &tx = std::get<0 >(txd);
352
+ CTxUndo &undo = std::get<1 >(txd);
353
+ CCoins &origcoins = std::get<2 >(txd);
354
+
355
+ uint256 undohash = tx.GetHash ();
356
+
357
+ // Update the expected result
358
+ // Remove new outputs
359
+ result[undohash].Clear ();
360
+ // If not coinbase restore prevout
361
+ if (!tx.IsCoinBase ()) {
362
+ result[tx.vin [0 ].prevout .hash ] = origcoins;
363
+ }
364
+
365
+ // Disconnect the tx from the current UTXO
366
+ // See code in DisconnectBlock
367
+ // remove outputs
368
+ {
369
+ CCoinsModifier outs = stack.back ()->ModifyCoins (undohash);
370
+ outs->Clear ();
371
+ }
372
+ // restore inputs
373
+ if (!tx.IsCoinBase ()) {
374
+ const COutPoint &out = tx.vin [0 ].prevout ;
375
+ const CTxInUndo &undoin = undo.vprevout [0 ];
376
+ ApplyTxInUndo (undoin, *(stack.back ()), out);
377
+ }
378
+ // Store as a candidate for reconnection
379
+ disconnectedids.insert (undohash);
380
+
381
+ // Update the utxoset
382
+ utxoset.erase (undohash);
383
+ if (!tx.IsCoinBase ())
384
+ utxoset.insert (tx.vin [0 ].prevout .hash );
303
385
}
304
386
305
387
// Once every 1000 iterations and at the end, verify the full cache.
@@ -308,9 +390,9 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
308
390
const CCoins* coins = stack.back ()->AccessCoins (it->first );
309
391
if (coins) {
310
392
BOOST_CHECK (*coins == it->second );
311
- } else {
393
+ } else {
312
394
BOOST_CHECK (it->second .IsPruned ());
313
- }
395
+ }
314
396
}
315
397
}
316
398
@@ -334,7 +416,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
334
416
tip = stack.back ();
335
417
}
336
418
stack.push_back (new CCoinsViewCacheTest (tip));
337
- }
419
+ }
338
420
}
339
421
}
340
422
@@ -420,6 +502,7 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
420
502
const static uint256 TXID;
421
503
const static CAmount PRUNED = -1 ;
422
504
const static CAmount ABSENT = -2 ;
505
+ const static CAmount FAIL = -3 ;
423
506
const static CAmount VALUE1 = 100 ;
424
507
const static CAmount VALUE2 = 200 ;
425
508
const static CAmount VALUE3 = 300 ;
@@ -630,11 +713,17 @@ BOOST_AUTO_TEST_CASE(ccoins_modify)
630
713
void CheckModifyNewCoinsBase (CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase)
631
714
{
632
715
SingleEntryCacheTest test (base_value, cache_value, cache_flags);
633
- SetCoinsValue (modify_value, *test.cache .ModifyNewCoins (TXID, coinbase));
634
716
635
717
CAmount result_value;
636
718
char result_flags;
637
- GetCoinsMapEntry (test.cache .map (), result_value, result_flags);
719
+ try {
720
+ SetCoinsValue (modify_value, *test.cache .ModifyNewCoins (TXID, coinbase));
721
+ GetCoinsMapEntry (test.cache .map (), result_value, result_flags);
722
+ } catch (std::logic_error& e) {
723
+ result_value = FAIL;
724
+ result_flags = NO_ENTRY;
725
+ }
726
+
638
727
BOOST_CHECK_EQUAL (result_value, expected_value);
639
728
BOOST_CHECK_EQUAL (result_flags, expected_flags);
640
729
}
@@ -669,33 +758,33 @@ BOOST_AUTO_TEST_CASE(ccoins_modify_new)
669
758
CheckModifyNewCoins (PRUNED, PRUNED, PRUNED, 0 , DIRTY , true );
670
759
CheckModifyNewCoins (PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY , false );
671
760
CheckModifyNewCoins (PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY , true );
672
- CheckModifyNewCoins (PRUNED, PRUNED, ABSENT , DIRTY , NO_ENTRY , false );
761
+ CheckModifyNewCoins (PRUNED, PRUNED, PRUNED , DIRTY , DIRTY , false );
673
762
CheckModifyNewCoins (PRUNED, PRUNED, PRUNED, DIRTY , DIRTY , true );
674
763
CheckModifyNewCoins (PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , false );
675
764
CheckModifyNewCoins (PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , true );
676
765
CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false );
677
766
CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, 0 , DIRTY , true );
678
767
CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false );
679
768
CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
680
- CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, DIRTY , DIRTY|FRESH , false );
769
+ CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , false );
681
770
CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , true );
682
771
CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false );
683
772
CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
684
- CheckModifyNewCoins (VALUE2, PRUNED, ABSENT , 0 , NO_ENTRY , false );
773
+ CheckModifyNewCoins (VALUE2, PRUNED, FAIL , 0 , NO_ENTRY , false );
685
774
CheckModifyNewCoins (VALUE2, PRUNED, PRUNED, 0 , DIRTY , true );
686
- CheckModifyNewCoins (VALUE2, PRUNED, ABSENT , FRESH , NO_ENTRY , false );
775
+ CheckModifyNewCoins (VALUE2, PRUNED, FAIL , FRESH , NO_ENTRY , false );
687
776
CheckModifyNewCoins (VALUE2, PRUNED, ABSENT, FRESH , NO_ENTRY , true );
688
- CheckModifyNewCoins (VALUE2, PRUNED, ABSENT , DIRTY , NO_ENTRY , false );
777
+ CheckModifyNewCoins (VALUE2, PRUNED, FAIL , DIRTY , NO_ENTRY , false );
689
778
CheckModifyNewCoins (VALUE2, PRUNED, PRUNED, DIRTY , DIRTY , true );
690
- CheckModifyNewCoins (VALUE2, PRUNED, ABSENT , DIRTY|FRESH, NO_ENTRY , false );
779
+ CheckModifyNewCoins (VALUE2, PRUNED, FAIL , DIRTY|FRESH, NO_ENTRY , false );
691
780
CheckModifyNewCoins (VALUE2, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , true );
692
- CheckModifyNewCoins (VALUE2, VALUE3, VALUE3 , 0 , DIRTY|FRESH , false );
781
+ CheckModifyNewCoins (VALUE2, VALUE3, FAIL , 0 , NO_ENTRY , false );
693
782
CheckModifyNewCoins (VALUE2, VALUE3, VALUE3, 0 , DIRTY , true );
694
- CheckModifyNewCoins (VALUE2, VALUE3, VALUE3 , FRESH , DIRTY|FRESH , false );
783
+ CheckModifyNewCoins (VALUE2, VALUE3, FAIL , FRESH , NO_ENTRY , false );
695
784
CheckModifyNewCoins (VALUE2, VALUE3, VALUE3, FRESH , DIRTY|FRESH, true );
696
- CheckModifyNewCoins (VALUE2, VALUE3, VALUE3 , DIRTY , DIRTY|FRESH , false );
785
+ CheckModifyNewCoins (VALUE2, VALUE3, FAIL , DIRTY , NO_ENTRY , false );
697
786
CheckModifyNewCoins (VALUE2, VALUE3, VALUE3, DIRTY , DIRTY , true );
698
- CheckModifyNewCoins (VALUE2, VALUE3, VALUE3 , DIRTY|FRESH, DIRTY|FRESH , false );
787
+ CheckModifyNewCoins (VALUE2, VALUE3, FAIL , DIRTY|FRESH, NO_ENTRY , false );
699
788
CheckModifyNewCoins (VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
700
789
}
701
790
0 commit comments