55#include " coins.h"
66#include " script/standard.h"
77#include " uint256.h"
8+ #include " undo.h"
89#include " utilstrencodings.h"
910#include " test/test_bitcoin.h"
1011#include " test/test_random.h"
1617
1718#include < boost/test/unit_test.hpp>
1819
20+ bool ApplyTxInUndo (const CTxInUndo& undo, CCoinsViewCache& view, const COutPoint& out);
21+ void UpdateCoins (const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight);
22+
1923namespace
2024{
2125class CCoinsViewTest : public CCoinsView
@@ -213,6 +217,22 @@ BOOST_AUTO_TEST_CASE(coins_cache_simulation_test)
213217 BOOST_CHECK (missed_an_entry);
214218}
215219
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+
216236// This test is similar to the previous test
217237// except the emphasis is on testing the functionality of UpdateCoins
218238// random txs are created and UpdateCoins is used to update the cache stack
@@ -229,77 +249,139 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
229249 std::vector<CCoinsViewCacheTest*> stack; // A stack of CCoinsViewCaches on top.
230250 stack.push_back (new CCoinsViewCacheTest (&base)); // Start with one cache.
231251
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 ;
235255 std::set<uint256> duplicateids;
256+ std::set<uint256> utxoset;
236257
237258 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 ) {
239263 CMutableTransaction tx;
240264 tx.vin .resize (1 );
241265 tx.vout .resize (1 );
242266 tx.vout [0 ].nValue = i; // Keep txs unique unless intended to duplicate
243267 unsigned int height = insecure_rand ();
268+ CCoins oldcoins;
244269
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
248273 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 ());
257281 }
258282 else {
259- coinbaseids[ tx.GetHash ()] = tx. vout [ 0 ]. nValue ;
283+ coinbaseids. insert ( tx.GetHash ()) ;
260284 }
261285 assert (CTransaction (tx).IsCoinBase ());
262286 }
263- // 9/10 times create a regular tx
287+
288+ // 17/20 times reconnect previous or add a regular tx
264289 else {
290+
265291 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 ());
270308 }
271- prevouthash = *txIt;
272309
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 ();
276314
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];
277322 // Update the expected result of prevouthash to know these coins are spent
278- CCoins& oldcoins = result[prevouthash];
279- oldcoins.Clear ();
323+ result[prevouthash].Clear ();
280324
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);
287326
288327 // The test is designed to ensure spending a duplicate coinbase will work properly
289328 // if that ever happens and not resurrect the previously overwritten coinbase
290329 if (duplicateids.count (prevouthash))
291330 spent_a_duplicate_coinbase = true ;
292331
293- assert (!CTransaction (tx).IsCoinBase ());
294332 }
295- // Track this tx to possibly spend later
296- alltxids.insert (tx.GetHash ());
297-
298333 // 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);
301339
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 );
303385 }
304386
305387 // Once every 1000 iterations and at the end, verify the full cache.
@@ -308,9 +390,9 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
308390 const CCoins* coins = stack.back ()->AccessCoins (it->first );
309391 if (coins) {
310392 BOOST_CHECK (*coins == it->second );
311- } else {
393+ } else {
312394 BOOST_CHECK (it->second .IsPruned ());
313- }
395+ }
314396 }
315397 }
316398
@@ -334,7 +416,7 @@ BOOST_AUTO_TEST_CASE(updatecoins_simulation_test)
334416 tip = stack.back ();
335417 }
336418 stack.push_back (new CCoinsViewCacheTest (tip));
337- }
419+ }
338420 }
339421 }
340422
@@ -420,6 +502,7 @@ BOOST_AUTO_TEST_CASE(ccoins_serialization)
420502const static uint256 TXID;
421503const static CAmount PRUNED = -1 ;
422504const static CAmount ABSENT = -2 ;
505+ const static CAmount FAIL = -3 ;
423506const static CAmount VALUE1 = 100 ;
424507const static CAmount VALUE2 = 200 ;
425508const static CAmount VALUE3 = 300 ;
@@ -630,11 +713,17 @@ BOOST_AUTO_TEST_CASE(ccoins_modify)
630713void CheckModifyNewCoinsBase (CAmount base_value, CAmount cache_value, CAmount modify_value, CAmount expected_value, char cache_flags, char expected_flags, bool coinbase)
631714{
632715 SingleEntryCacheTest test (base_value, cache_value, cache_flags);
633- SetCoinsValue (modify_value, *test.cache .ModifyNewCoins (TXID, coinbase));
634716
635717 CAmount result_value;
636718 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+
638727 BOOST_CHECK_EQUAL (result_value, expected_value);
639728 BOOST_CHECK_EQUAL (result_flags, expected_flags);
640729}
@@ -669,33 +758,33 @@ BOOST_AUTO_TEST_CASE(ccoins_modify_new)
669758 CheckModifyNewCoins (PRUNED, PRUNED, PRUNED, 0 , DIRTY , true );
670759 CheckModifyNewCoins (PRUNED, PRUNED, ABSENT, FRESH , NO_ENTRY , false );
671760 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 );
673762 CheckModifyNewCoins (PRUNED, PRUNED, PRUNED, DIRTY , DIRTY , true );
674763 CheckModifyNewCoins (PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , false );
675764 CheckModifyNewCoins (PRUNED, PRUNED, ABSENT, DIRTY|FRESH, NO_ENTRY , true );
676765 CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, 0 , DIRTY|FRESH, false );
677766 CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, 0 , DIRTY , true );
678767 CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, FRESH , DIRTY|FRESH, false );
679768 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 );
681770 CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, DIRTY , DIRTY , true );
682771 CheckModifyNewCoins (PRUNED, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, false );
683772 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 );
685774 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 );
687776 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 );
689778 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 );
691780 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 );
693782 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 );
695784 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 );
697786 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 );
699788 CheckModifyNewCoins (VALUE2, VALUE3, VALUE3, DIRTY|FRESH, DIRTY|FRESH, true );
700789}
701790
0 commit comments