diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index dc1360724..e507a2d43 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -1,1141 +1,1139 @@ // Copyright (c) 2011-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <txmempool.h> #include <policy/policy.h> #include <reverse_iterator.h> #include <util/system.h> #include <util/time.h> #include <test/setup_common.h> #include <boost/test/unit_test.hpp> #include <algorithm> #include <vector> BOOST_FIXTURE_TEST_SUITE(mempool_tests, TestingSetup) BOOST_AUTO_TEST_CASE(TestPackageAccounting) { CTxMemPool testPool; LOCK2(cs_main, testPool.cs); TestMemPoolEntryHelper entry; CMutableTransaction parentOfAll; std::vector<CTxIn> outpoints; const size_t maxOutputs = 3; // Construct a parent for the rest of the chain parentOfAll.vin.resize(1); parentOfAll.vin[0].scriptSig = CScript(); // Give us a couple outpoints so we can spend them for (size_t i = 0; i < maxOutputs; i++) { parentOfAll.vout.emplace_back(10 * SATOSHI, CScript() << OP_TRUE); } TxId parentOfAllId = parentOfAll.GetId(); testPool.addUnchecked(entry.SigOpCount(0).FromTx(parentOfAll)); // Add some outpoints to the tracking vector for (size_t i = 0; i < maxOutputs; i++) { outpoints.emplace_back(COutPoint(parentOfAllId, i)); } Amount totalFee = Amount::zero(); size_t totalSize = CTransaction(parentOfAll).GetTotalSize(); size_t totalVirtualSize = totalSize; int64_t totalSigOpCount = 0; // Generate 100 transactions for (size_t totalTransactions = 0; totalTransactions < 100; totalTransactions++) { CMutableTransaction tx; uint64_t minAncestors = std::numeric_limits<size_t>::max(); uint64_t maxAncestors = 0; Amount minFees = MAX_MONEY; Amount maxFees = Amount::zero(); uint64_t minSize = std::numeric_limits<size_t>::max(); uint64_t maxSize = 0; uint64_t minVirtualSize = std::numeric_limits<size_t>::max(); uint64_t maxVirtualSize = 0; int64_t minSigOpCount = std::numeric_limits<int64_t>::max(); int64_t maxSigOpCount = 0; // Consume random inputs, but make sure we don't consume more than // available for (size_t input = std::min(InsecureRandRange(maxOutputs) + 1, uint64_t(outpoints.size())); input > 0; input--) { std::swap(outpoints[InsecureRandRange(outpoints.size())], outpoints.back()); tx.vin.emplace_back(outpoints.back()); outpoints.pop_back(); // We don't know exactly how many ancestors this transaction has // due to possible duplicates. Calculate a valid range based on // parents. CTxMemPoolEntry parent = *testPool.mapTx.find(tx.vin.back().prevout.GetTxId()); minAncestors = std::min(minAncestors, parent.GetCountWithAncestors()); maxAncestors += parent.GetCountWithAncestors(); minFees = std::min(minFees, parent.GetModFeesWithAncestors()); maxFees += parent.GetModFeesWithAncestors(); minSize = std::min(minSize, parent.GetSizeWithAncestors()); maxSize += parent.GetSizeWithAncestors(); minVirtualSize = std::min(minSize, parent.GetVirtualSizeWithAncestors()); maxVirtualSize += parent.GetVirtualSizeWithAncestors(); minSigOpCount = std::min(minSigOpCount, parent.GetSigOpCountWithAncestors()); maxSigOpCount += parent.GetSigOpCountWithAncestors(); } // Produce random number of outputs for (size_t output = InsecureRandRange(maxOutputs) + 1; output > 0; output--) { tx.vout.emplace_back(10 * SATOSHI, CScript() << OP_TRUE); } TxId curId = tx.GetId(); // Record the outputs for (size_t output = tx.vout.size(); output > 0; output--) { outpoints.emplace_back(COutPoint(curId, output)); } Amount randFee = int64_t(InsecureRandRange(300)) * SATOSHI; int randSigOpCount = InsecureRandRange(5); testPool.addUnchecked( entry.Fee(randFee).SigOpCount(randSigOpCount).FromTx(tx)); // Add this transaction to the totals. minAncestors += 1; maxAncestors += 1; minFees += randFee; maxFees += randFee; minSize += CTransaction(tx).GetTotalSize(); maxSize += CTransaction(tx).GetTotalSize(); // virtualsize is a nonlinear function of its arguments, so we can't // make as strong guarantees about its range; but assuming virtualsize // is monotonically increasing in each argument, we can say the // following: minVirtualSize += 0; maxVirtualSize += GetVirtualTransactionSize( CTransaction(tx).GetTotalSize(), randSigOpCount); minSigOpCount += randSigOpCount; maxSigOpCount += randSigOpCount; // Calculate overall values totalFee += randFee; totalSize += CTransaction(tx).GetTotalSize(); totalVirtualSize += GetVirtualTransactionSize( CTransaction(tx).GetTotalSize(), randSigOpCount); totalSigOpCount += randSigOpCount; CTxMemPoolEntry parentEntry = *testPool.mapTx.find(parentOfAllId); CTxMemPoolEntry latestEntry = *testPool.mapTx.find(curId); // Based on size/sigops ranges we can compute more strict bounds for the // virtual size ranges/totals, assuming virtualsize is monotonic in each // argument. uint64_t minVirtualSize_strict = GetVirtualTransactionSize(minSize, minSigOpCount); uint64_t maxVirtualSize_strict = GetVirtualTransactionSize(maxSize, maxSigOpCount); uint64_t totalVirtualSize_strict = GetVirtualTransactionSize(totalSize, totalSigOpCount); // these are as-good or better than the earlier estimations. BOOST_CHECK(minVirtualSize_strict >= minVirtualSize); BOOST_CHECK(maxVirtualSize_strict <= maxVirtualSize); BOOST_CHECK(totalVirtualSize_strict <= totalVirtualSize); // Ensure values are within the expected ranges BOOST_CHECK(latestEntry.GetCountWithAncestors() >= minAncestors); BOOST_CHECK(latestEntry.GetCountWithAncestors() <= maxAncestors); BOOST_CHECK(latestEntry.GetSizeWithAncestors() >= minSize); BOOST_CHECK(latestEntry.GetSizeWithAncestors() <= maxSize); BOOST_CHECK(latestEntry.GetVirtualSizeWithAncestors() >= minVirtualSize_strict); BOOST_CHECK(latestEntry.GetVirtualSizeWithAncestors() <= maxVirtualSize_strict); BOOST_CHECK(latestEntry.GetSigOpCountWithAncestors() >= minSigOpCount); BOOST_CHECK(latestEntry.GetSigOpCountWithAncestors() <= maxSigOpCount); BOOST_CHECK(latestEntry.GetModFeesWithAncestors() >= minFees); BOOST_CHECK(latestEntry.GetModFeesWithAncestors() <= maxFees); BOOST_CHECK_EQUAL(parentEntry.GetCountWithDescendants(), testPool.mapTx.size()); BOOST_CHECK_EQUAL(parentEntry.GetSizeWithDescendants(), totalSize); BOOST_CHECK_EQUAL(parentEntry.GetVirtualSizeWithDescendants(), totalVirtualSize_strict); BOOST_CHECK_EQUAL(parentEntry.GetModFeesWithDescendants(), totalFee); BOOST_CHECK_EQUAL(parentEntry.GetSigOpCountWithDescendants(), totalSigOpCount); } } BOOST_AUTO_TEST_CASE(MempoolRemoveTest) { // Test CTxMemPool::remove functionality TestMemPoolEntryHelper entry; // Parent transaction with three children, and three grand-children: CMutableTransaction txParent; txParent.vin.resize(1); txParent.vin[0].scriptSig = CScript() << OP_11; txParent.vout.resize(3); for (int i = 0; i < 3; i++) { txParent.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL; txParent.vout[i].nValue = 33000 * SATOSHI; } CMutableTransaction txChild[3]; for (int i = 0; i < 3; i++) { txChild[i].vin.resize(1); txChild[i].vin[0].scriptSig = CScript() << OP_11; txChild[i].vin[0].prevout = COutPoint(txParent.GetId(), i); txChild[i].vout.resize(1); txChild[i].vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; txChild[i].vout[0].nValue = 11000 * SATOSHI; } CMutableTransaction txGrandChild[3]; for (int i = 0; i < 3; i++) { txGrandChild[i].vin.resize(1); txGrandChild[i].vin[0].scriptSig = CScript() << OP_11; txGrandChild[i].vin[0].prevout = COutPoint(txChild[i].GetId(), 0); txGrandChild[i].vout.resize(1); txGrandChild[i].vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; txGrandChild[i].vout[0].nValue = 11000 * SATOSHI; } CTxMemPool testPool; LOCK2(cs_main, testPool.cs); // Nothing in pool, remove should do nothing: unsigned int poolSize = testPool.size(); testPool.removeRecursive(CTransaction(txParent)); BOOST_CHECK_EQUAL(testPool.size(), poolSize); // Just the parent: testPool.addUnchecked(entry.FromTx(txParent)); poolSize = testPool.size(); testPool.removeRecursive(CTransaction(txParent)); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 1); // Parent, children, grandchildren: testPool.addUnchecked(entry.FromTx(txParent)); for (int i = 0; i < 3; i++) { testPool.addUnchecked(entry.FromTx(txChild[i])); testPool.addUnchecked(entry.FromTx(txGrandChild[i])); } // Remove Child[0], GrandChild[0] should be removed: poolSize = testPool.size(); testPool.removeRecursive(CTransaction(txChild[0])); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 2); // ... make sure grandchild and child are gone: poolSize = testPool.size(); testPool.removeRecursive(CTransaction(txGrandChild[0])); BOOST_CHECK_EQUAL(testPool.size(), poolSize); poolSize = testPool.size(); testPool.removeRecursive(CTransaction(txChild[0])); BOOST_CHECK_EQUAL(testPool.size(), poolSize); // Remove parent, all children/grandchildren should go: poolSize = testPool.size(); testPool.removeRecursive(CTransaction(txParent)); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 5); BOOST_CHECK_EQUAL(testPool.size(), 0UL); // Add children and grandchildren, but NOT the parent (simulate the parent // being in a block) for (int i = 0; i < 3; i++) { testPool.addUnchecked(entry.FromTx(txChild[i])); testPool.addUnchecked(entry.FromTx(txGrandChild[i])); } // Now remove the parent, as might happen if a block-re-org occurs but the // parent cannot be put into the mempool (maybe because it is non-standard): poolSize = testPool.size(); testPool.removeRecursive(CTransaction(txParent)); BOOST_CHECK_EQUAL(testPool.size(), poolSize - 6); BOOST_CHECK_EQUAL(testPool.size(), 0UL); } BOOST_AUTO_TEST_CASE(MempoolClearTest) { // Test CTxMemPool::clear functionality TestMemPoolEntryHelper entry; // Create a transaction CMutableTransaction txParent; txParent.vin.resize(1); txParent.vin[0].scriptSig = CScript() << OP_11; txParent.vout.resize(3); for (int i = 0; i < 3; i++) { txParent.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL; txParent.vout[i].nValue = 33000 * SATOSHI; } CTxMemPool testPool; LOCK2(cs_main, testPool.cs); // Nothing in pool, clear should do nothing: testPool.clear(); BOOST_CHECK_EQUAL(testPool.size(), 0UL); // Add the transaction testPool.addUnchecked(entry.FromTx(txParent)); BOOST_CHECK_EQUAL(testPool.size(), 1UL); BOOST_CHECK_EQUAL(testPool.mapTx.size(), 1UL); BOOST_CHECK_EQUAL(testPool.mapNextTx.size(), 1UL); BOOST_CHECK_EQUAL(testPool.vTxHashes.size(), 1UL); // CTxMemPool's members should be empty after a clear testPool.clear(); BOOST_CHECK_EQUAL(testPool.size(), 0UL); BOOST_CHECK_EQUAL(testPool.mapTx.size(), 0UL); BOOST_CHECK_EQUAL(testPool.mapNextTx.size(), 0UL); BOOST_CHECK_EQUAL(testPool.vTxHashes.size(), 0UL); } template <typename name> static void CheckSort(CTxMemPool &pool, std::vector<std::string> &sortedOrder, const std::string &testcase) EXCLUSIVE_LOCKS_REQUIRED(pool.cs) { BOOST_CHECK_EQUAL(pool.size(), sortedOrder.size()); typename CTxMemPool::indexed_transaction_set::index<name>::type::iterator it = pool.mapTx.get<name>().begin(); int count = 0; for (; it != pool.mapTx.get<name>().end(); ++it, ++count) { BOOST_CHECK_MESSAGE(it->GetTx().GetId().ToString() == sortedOrder[count], it->GetTx().GetId().ToString() << " != " << sortedOrder[count] << " in test " << testcase << ":" << count); } } BOOST_AUTO_TEST_CASE(MempoolIndexingTest) { CTxMemPool pool; LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; /** * Remove the default nonzero sigops, since the below tests are focussing on * fee-based ordering and involve some artificially very tiny 21-byte * transactions without any inputs. */ entry.SigOpCount(0); /* 3rd highest fee */ CMutableTransaction tx1 = CMutableTransaction(); tx1.vout.resize(1); tx1.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx1.vout[0].nValue = 10 * COIN; pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tx1)); /* highest fee */ CMutableTransaction tx2 = CMutableTransaction(); tx2.vout.resize(1); tx2.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx2.vout[0].nValue = 2 * COIN; pool.addUnchecked(entry.Fee(20000 * SATOSHI).FromTx(tx2)); /* lowest fee */ CMutableTransaction tx3 = CMutableTransaction(); tx3.vout.resize(1); tx3.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx3.vout[0].nValue = 5 * COIN; pool.addUnchecked(entry.Fee(Amount::zero()).FromTx(tx3)); /* 2nd highest fee */ CMutableTransaction tx4 = CMutableTransaction(); tx4.vout.resize(1); tx4.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx4.vout[0].nValue = 6 * COIN; pool.addUnchecked(entry.Fee(15000 * SATOSHI).FromTx(tx4)); /* equal fee rate to tx1, but newer */ CMutableTransaction tx5 = CMutableTransaction(); tx5.vout.resize(1); tx5.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx5.vout[0].nValue = 11 * COIN; entry.nTime = 1; pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tx5)); BOOST_CHECK_EQUAL(pool.size(), 5UL); std::vector<std::string> sortedOrder; sortedOrder.resize(5); sortedOrder[0] = tx3.GetId().ToString(); // 0 sortedOrder[1] = tx5.GetId().ToString(); // 10000 sortedOrder[2] = tx1.GetId().ToString(); // 10000 sortedOrder[3] = tx4.GetId().ToString(); // 15000 sortedOrder[4] = tx2.GetId().ToString(); // 20000 CheckSort<descendant_score>(pool, sortedOrder, "MempoolIndexingTest1"); /* low fee but with high fee child */ /* tx6 -> tx7 -> tx8, tx9 -> tx10 */ CMutableTransaction tx6 = CMutableTransaction(); tx6.vout.resize(1); tx6.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx6.vout[0].nValue = 20 * COIN; pool.addUnchecked(entry.Fee(Amount::zero()).FromTx(tx6)); BOOST_CHECK_EQUAL(pool.size(), 6UL); // Check that at this point, tx6 is sorted low sortedOrder.insert(sortedOrder.begin(), tx6.GetId().ToString()); CheckSort<descendant_score>(pool, sortedOrder, "MempoolIndexingTest2"); CTxMemPool::setEntries setAncestors; setAncestors.insert(pool.mapTx.find(tx6.GetId())); CMutableTransaction tx7 = CMutableTransaction(); tx7.vin.resize(1); tx7.vin[0].prevout = COutPoint(tx6.GetId(), 0); tx7.vin[0].scriptSig = CScript() << OP_11; tx7.vout.resize(2); tx7.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx7.vout[0].nValue = 10 * COIN; tx7.vout[1].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx7.vout[1].nValue = 1 * COIN; CTxMemPool::setEntries setAncestorsCalculated; std::string dummy; BOOST_CHECK_EQUAL( pool.CalculateMemPoolAncestors(entry.Fee(2000000 * SATOSHI).FromTx(tx7), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true); BOOST_CHECK(setAncestorsCalculated == setAncestors); pool.addUnchecked(entry.FromTx(tx7), setAncestors); BOOST_CHECK_EQUAL(pool.size(), 7UL); // Now tx6 should be sorted higher (high fee child): tx7, tx6, tx2, ... sortedOrder.erase(sortedOrder.begin()); sortedOrder.push_back(tx6.GetId().ToString()); sortedOrder.push_back(tx7.GetId().ToString()); CheckSort<descendant_score>(pool, sortedOrder, "MempoolIndexingTest3"); /* low fee child of tx7 */ CMutableTransaction tx8 = CMutableTransaction(); tx8.vin.resize(1); tx8.vin[0].prevout = COutPoint(tx7.GetId(), 0); tx8.vin[0].scriptSig = CScript() << OP_11; tx8.vout.resize(1); tx8.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx8.vout[0].nValue = 10 * COIN; setAncestors.insert(pool.mapTx.find(tx7.GetId())); pool.addUnchecked(entry.Fee(Amount::zero()).Time(2).FromTx(tx8), setAncestors); // Now tx8 should be sorted low, but tx6/tx both high sortedOrder.insert(sortedOrder.begin(), tx8.GetId().ToString()); CheckSort<descendant_score>(pool, sortedOrder, "MempoolIndexingTest4"); /* low fee child of tx7 */ CMutableTransaction tx9 = CMutableTransaction(); tx9.vin.resize(1); tx9.vin[0].prevout = COutPoint(tx7.GetId(), 1); tx9.vin[0].scriptSig = CScript() << OP_11; tx9.vout.resize(1); tx9.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx9.vout[0].nValue = 1 * COIN; pool.addUnchecked(entry.Fee(Amount::zero()).Time(3).FromTx(tx9), setAncestors); // tx9 should be sorted low BOOST_CHECK_EQUAL(pool.size(), 9UL); sortedOrder.insert(sortedOrder.begin(), tx9.GetId().ToString()); CheckSort<descendant_score>(pool, sortedOrder, "MempoolIndexingTest5"); std::vector<std::string> snapshotOrder = sortedOrder; setAncestors.insert(pool.mapTx.find(tx8.GetId())); setAncestors.insert(pool.mapTx.find(tx9.GetId())); /* tx10 depends on tx8 and tx9 and has a high fee*/ CMutableTransaction tx10 = CMutableTransaction(); tx10.vin.resize(2); tx10.vin[0].prevout = COutPoint(tx8.GetId(), 0); tx10.vin[0].scriptSig = CScript() << OP_11; tx10.vin[1].prevout = COutPoint(tx9.GetId(), 0); tx10.vin[1].scriptSig = CScript() << OP_11; tx10.vout.resize(1); tx10.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx10.vout[0].nValue = 10 * COIN; setAncestorsCalculated.clear(); BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors( entry.Fee(200000 * SATOSHI).Time(4).FromTx(tx10), setAncestorsCalculated, 100, 1000000, 1000, 1000000, dummy), true); BOOST_CHECK(setAncestorsCalculated == setAncestors); pool.addUnchecked(entry.FromTx(tx10), setAncestors); /** * tx8 and tx9 should both now be sorted higher * Final order after tx10 is added: * * tx3 = 0 (1) * tx5 = 10000 (1) * tx1 = 10000 (1) * tx4 = 15000 (1) * tx2 = 20000 (1) * tx9 = 200k (2 txs) * tx8 = 200k (2 txs) * tx10 = 200k (1 tx) * tx6 = 2.2M (5 txs) * tx7 = 2.2M (4 txs) */ // take out tx9, tx8 from the beginning sortedOrder.erase(sortedOrder.begin(), sortedOrder.begin() + 2); sortedOrder.insert(sortedOrder.begin() + 5, tx9.GetId().ToString()); sortedOrder.insert(sortedOrder.begin() + 6, tx8.GetId().ToString()); // tx10 is just before tx6 sortedOrder.insert(sortedOrder.begin() + 7, tx10.GetId().ToString()); CheckSort<descendant_score>(pool, sortedOrder, "MempoolIndexingTest6"); // there should be 10 transactions in the mempool BOOST_CHECK_EQUAL(pool.size(), 10UL); // Now try removing tx10 and verify the sort order returns to normal pool.removeRecursive(pool.mapTx.find(tx10.GetId())->GetTx()); CheckSort<descendant_score>(pool, snapshotOrder, "MempoolIndexingTest7"); pool.removeRecursive(pool.mapTx.find(tx9.GetId())->GetTx()); pool.removeRecursive(pool.mapTx.find(tx8.GetId())->GetTx()); } BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) { CTxMemPool pool; LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; /** * Remove the default nonzero sigops, since the below tests are focussing on * fee-based ordering and involve some artificially very tiny 21-byte * transactions without any inputs. */ entry.SigOpCount(0); /* 3rd highest fee */ CMutableTransaction tx1 = CMutableTransaction(); tx1.vout.resize(1); tx1.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx1.vout[0].nValue = 10 * COIN; pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tx1)); /* highest fee */ CMutableTransaction tx2 = CMutableTransaction(); tx2.vout.resize(1); tx2.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx2.vout[0].nValue = 2 * COIN; pool.addUnchecked(entry.Fee(20000 * SATOSHI).FromTx(tx2)); uint64_t tx2Size = CTransaction(tx2).GetTotalSize(); /* lowest fee */ CMutableTransaction tx3 = CMutableTransaction(); tx3.vout.resize(1); tx3.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx3.vout[0].nValue = 5 * COIN; pool.addUnchecked(entry.Fee(Amount::zero()).FromTx(tx3)); /* 2nd highest fee */ CMutableTransaction tx4 = CMutableTransaction(); tx4.vout.resize(1); tx4.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx4.vout[0].nValue = 6 * COIN; pool.addUnchecked(entry.Fee(15000 * SATOSHI).FromTx(tx4)); /* equal fee rate to tx1, but newer */ CMutableTransaction tx5 = CMutableTransaction(); tx5.vout.resize(1); tx5.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx5.vout[0].nValue = 11 * COIN; pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tx5)); BOOST_CHECK_EQUAL(pool.size(), 5UL); std::vector<std::string> sortedOrder; sortedOrder.resize(5); sortedOrder[0] = tx2.GetId().ToString(); // 20000 sortedOrder[1] = tx4.GetId().ToString(); // 15000 // tx1 and tx5 are both 10000 // Ties are broken by hash, not timestamp, so determine which hash comes // first. if (tx1.GetId() < tx5.GetId()) { sortedOrder[2] = tx1.GetId().ToString(); sortedOrder[3] = tx5.GetId().ToString(); } else { sortedOrder[2] = tx5.GetId().ToString(); sortedOrder[3] = tx1.GetId().ToString(); } sortedOrder[4] = tx3.GetId().ToString(); // 0 CheckSort<ancestor_score>(pool, sortedOrder, "MempoolAncestorIndexingTest1"); /* low fee parent with high fee child */ /* tx6 (0) -> tx7 (high) */ CMutableTransaction tx6 = CMutableTransaction(); tx6.vout.resize(1); tx6.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx6.vout[0].nValue = 20 * COIN; uint64_t tx6Size = CTransaction(tx6).GetTotalSize(); pool.addUnchecked(entry.Fee(Amount::zero()).FromTx(tx6)); BOOST_CHECK_EQUAL(pool.size(), 6UL); // Ties are broken by hash if (tx3.GetId() < tx6.GetId()) { sortedOrder.push_back(tx6.GetId().ToString()); } else { sortedOrder.insert(sortedOrder.end() - 1, tx6.GetId().ToString()); } CheckSort<ancestor_score>(pool, sortedOrder, "MempoolAncestorIndexingTest2"); CMutableTransaction tx7 = CMutableTransaction(); tx7.vin.resize(1); tx7.vin[0].prevout = COutPoint(tx6.GetId(), 0); tx7.vin[0].scriptSig = CScript() << OP_11; tx7.vout.resize(1); tx7.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx7.vout[0].nValue = 10 * COIN; uint64_t tx7Size = CTransaction(tx7).GetTotalSize(); /* set the fee to just below tx2's feerate when including ancestor */ Amount fee = int64_t((20000 / tx2Size) * (tx7Size + tx6Size) - 1) * SATOSHI; // CTxMemPoolEntry entry7(tx7, fee, 2, 10.0, 1, true); pool.addUnchecked(entry.Fee(Amount(fee)).FromTx(tx7)); BOOST_CHECK_EQUAL(pool.size(), 7UL); sortedOrder.insert(sortedOrder.begin() + 1, tx7.GetId().ToString()); CheckSort<ancestor_score>(pool, sortedOrder, "MempoolAncestorIndexingTest3"); /* after tx6 is mined, tx7 should move up in the sort */ std::vector<CTransactionRef> vtx; vtx.push_back(MakeTransactionRef(tx6)); pool.removeForBlock(vtx, 1); sortedOrder.erase(sortedOrder.begin() + 1); // Ties are broken by hash if (tx3.GetId() < tx6.GetId()) { sortedOrder.pop_back(); } else { sortedOrder.erase(sortedOrder.end() - 2); } sortedOrder.insert(sortedOrder.begin(), tx7.GetId().ToString()); CheckSort<ancestor_score>(pool, sortedOrder, "MempoolAncestorIndexingTest4"); // High-fee parent, low-fee child // tx7 -> tx8 CMutableTransaction tx8 = CMutableTransaction(); tx8.vin.resize(1); tx8.vin[0].prevout = COutPoint(tx7.GetId(), 0); tx8.vin[0].scriptSig = CScript() << OP_11; tx8.vout.resize(1); tx8.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx8.vout[0].nValue = 10 * COIN; // Check that we sort by min(feerate, ancestor_feerate): // set the fee so that the ancestor feerate is above tx1/5, // but the transaction's own feerate is lower pool.addUnchecked(entry.Fee(Amount(5000 * SATOSHI)).FromTx(tx8)); sortedOrder.insert(sortedOrder.end() - 1, tx8.GetId().ToString()); CheckSort<ancestor_score>(pool, sortedOrder, "MempoolAncestorIndexingTest5"); } BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest) { CTxMemPool pool; LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; Amount feeIncrement = MEMPOOL_FULL_FEE_INCREMENT.GetFeePerK(); CMutableTransaction tx1 = CMutableTransaction(); tx1.vin.resize(1); tx1.vin[0].scriptSig = CScript() << OP_1; tx1.vout.resize(1); tx1.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL; tx1.vout[0].nValue = 10 * COIN; pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tx1)); CMutableTransaction tx2 = CMutableTransaction(); tx2.vin.resize(1); tx2.vin[0].scriptSig = CScript() << OP_2; tx2.vout.resize(1); tx2.vout[0].scriptPubKey = CScript() << OP_2 << OP_EQUAL; tx2.vout[0].nValue = 10 * COIN; pool.addUnchecked(entry.Fee(5000 * SATOSHI).FromTx(tx2)); // should do nothing pool.TrimToSize(pool.DynamicMemoryUsage()); BOOST_CHECK(pool.exists(tx1.GetId())); BOOST_CHECK(pool.exists(tx2.GetId())); // should remove the lower-feerate transaction pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); BOOST_CHECK(pool.exists(tx1.GetId())); BOOST_CHECK(!pool.exists(tx2.GetId())); pool.addUnchecked(entry.FromTx(tx2)); CMutableTransaction tx3 = CMutableTransaction(); tx3.vin.resize(1); tx3.vin[0].prevout = COutPoint(tx2.GetId(), 0); tx3.vin[0].scriptSig = CScript() << OP_2; tx3.vout.resize(1); tx3.vout[0].scriptPubKey = CScript() << OP_3 << OP_EQUAL; tx3.vout[0].nValue = 10 * COIN; pool.addUnchecked(entry.Fee(20000 * SATOSHI).FromTx(tx3)); // tx3 should pay for tx2 (CPFP) pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); BOOST_CHECK(!pool.exists(tx1.GetId())); BOOST_CHECK(pool.exists(tx2.GetId())); BOOST_CHECK(pool.exists(tx3.GetId())); // mempool is limited to tx1's size in memory usage, so nothing fits pool.TrimToSize(CTransaction(tx1).GetTotalSize()); BOOST_CHECK(!pool.exists(tx1.GetId())); BOOST_CHECK(!pool.exists(tx2.GetId())); BOOST_CHECK(!pool.exists(tx3.GetId())); CFeeRate maxFeeRateRemoved(25000 * SATOSHI, CTransaction(tx3).GetTotalSize() + CTransaction(tx2).GetTotalSize()); BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + feeIncrement); CMutableTransaction tx4 = CMutableTransaction(); tx4.vin.resize(2); tx4.vin[0].prevout = COutPoint(); tx4.vin[0].scriptSig = CScript() << OP_4; tx4.vin[1].prevout = COutPoint(); tx4.vin[1].scriptSig = CScript() << OP_4; tx4.vout.resize(2); tx4.vout[0].scriptPubKey = CScript() << OP_4 << OP_EQUAL; tx4.vout[0].nValue = 10 * COIN; tx4.vout[1].scriptPubKey = CScript() << OP_4 << OP_EQUAL; tx4.vout[1].nValue = 10 * COIN; CMutableTransaction tx5 = CMutableTransaction(); tx5.vin.resize(2); tx5.vin[0].prevout = COutPoint(tx4.GetId(), 0); tx5.vin[0].scriptSig = CScript() << OP_4; tx5.vin[1].prevout = COutPoint(); tx5.vin[1].scriptSig = CScript() << OP_5; tx5.vout.resize(2); tx5.vout[0].scriptPubKey = CScript() << OP_5 << OP_EQUAL; tx5.vout[0].nValue = 10 * COIN; tx5.vout[1].scriptPubKey = CScript() << OP_5 << OP_EQUAL; tx5.vout[1].nValue = 10 * COIN; CMutableTransaction tx6 = CMutableTransaction(); tx6.vin.resize(2); tx6.vin[0].prevout = COutPoint(tx4.GetId(), 1); tx6.vin[0].scriptSig = CScript() << OP_4; tx6.vin[1].prevout = COutPoint(); tx6.vin[1].scriptSig = CScript() << OP_6; tx6.vout.resize(2); tx6.vout[0].scriptPubKey = CScript() << OP_6 << OP_EQUAL; tx6.vout[0].nValue = 10 * COIN; tx6.vout[1].scriptPubKey = CScript() << OP_6 << OP_EQUAL; tx6.vout[1].nValue = 10 * COIN; CMutableTransaction tx7 = CMutableTransaction(); tx7.vin.resize(2); tx7.vin[0].prevout = COutPoint(tx5.GetId(), 0); tx7.vin[0].scriptSig = CScript() << OP_5; tx7.vin[1].prevout = COutPoint(tx6.GetId(), 0); tx7.vin[1].scriptSig = CScript() << OP_6; tx7.vout.resize(2); tx7.vout[0].scriptPubKey = CScript() << OP_7 << OP_EQUAL; tx7.vout[0].nValue = 10 * COIN; tx7.vout[1].scriptPubKey = CScript() << OP_7 << OP_EQUAL; tx7.vout[1].nValue = 10 * COIN; pool.addUnchecked(entry.Fee(7000 * SATOSHI).FromTx(tx4)); pool.addUnchecked(entry.Fee(1000 * SATOSHI).FromTx(tx5)); pool.addUnchecked(entry.Fee(1100 * SATOSHI).FromTx(tx6)); pool.addUnchecked(entry.Fee(9000 * SATOSHI).FromTx(tx7)); // we only require this to remove, at max, 2 txn, because it's not clear // what we're really optimizing for aside from that pool.TrimToSize(pool.DynamicMemoryUsage() - 1); BOOST_CHECK(pool.exists(tx4.GetId())); BOOST_CHECK(pool.exists(tx6.GetId())); BOOST_CHECK(!pool.exists(tx7.GetId())); if (!pool.exists(tx5.GetId())) { pool.addUnchecked(entry.Fee(1000 * SATOSHI).FromTx(tx5)); } pool.addUnchecked(entry.Fee(9000 * SATOSHI).FromTx(tx7)); // should maximize mempool size by only removing 5/7 pool.TrimToSize(pool.DynamicMemoryUsage() / 2); BOOST_CHECK(pool.exists(tx4.GetId())); BOOST_CHECK(!pool.exists(tx5.GetId())); BOOST_CHECK(pool.exists(tx6.GetId())); BOOST_CHECK(!pool.exists(tx7.GetId())); pool.addUnchecked(entry.Fee(1000 * SATOSHI).FromTx(tx5)); pool.addUnchecked(entry.Fee(9000 * SATOSHI).FromTx(tx7)); std::vector<CTransactionRef> vtx; SetMockTime(42); SetMockTime(42 + CTxMemPool::ROLLING_FEE_HALFLIFE); BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + feeIncrement); // ... we should keep the same min fee until we get a block pool.removeForBlock(vtx, 1); SetMockTime(42 + 2 * CTxMemPool::ROLLING_FEE_HALFLIFE); BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), (maxFeeRateRemoved.GetFeePerK() + feeIncrement) / 2); // ... then feerate should drop 1/2 each halflife SetMockTime(42 + 2 * CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE / 2); BOOST_CHECK_EQUAL( pool.GetMinFee(pool.DynamicMemoryUsage() * 5 / 2).GetFeePerK(), (maxFeeRateRemoved.GetFeePerK() + feeIncrement) / 4); // ... with a 1/2 halflife when mempool is < 1/2 its target size SetMockTime(42 + 2 * CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE / 2 + CTxMemPool::ROLLING_FEE_HALFLIFE / 4); BOOST_CHECK_EQUAL( pool.GetMinFee(pool.DynamicMemoryUsage() * 9 / 2).GetFeePerK(), (maxFeeRateRemoved.GetFeePerK() + feeIncrement) / 8 + SATOSHI); // ... with a 1/4 halflife when mempool is < 1/4 its target size - - SetMockTime(0); } // expectedSize can be smaller than correctlyOrderedIds.size(), since we // might be testing intermediary states. Just avoiding some slice operations, void CheckDisconnectPoolOrder(DisconnectedBlockTransactions &disconnectPool, std::vector<TxId> correctlyOrderedIds, unsigned int expectedSize) { int i = 0; BOOST_CHECK_EQUAL(disconnectPool.GetQueuedTx().size(), expectedSize); // Txns in queuedTx's insertion_order index are sorted from children to // parent txn for (const CTransactionRef &tx : reverse_iterate(disconnectPool.GetQueuedTx().get<insertion_order>())) { BOOST_CHECK(tx->GetId() == correctlyOrderedIds[i]); i++; } } typedef std::vector<CMutableTransaction *> vecptx; BOOST_AUTO_TEST_CASE(TestImportMempool) { CMutableTransaction chainedTxn[5]; std::vector<TxId> correctlyOrderedIds; COutPoint lastOutpoint; // Construct a chain of 5 transactions for (int i = 0; i < 5; i++) { chainedTxn[i].vin.emplace_back(lastOutpoint); chainedTxn[i].vout.emplace_back(10 * SATOSHI, CScript() << OP_TRUE); correctlyOrderedIds.push_back(chainedTxn[i].GetId()); lastOutpoint = COutPoint(correctlyOrderedIds[i], 0); } // The first 3 txns simulate once confirmed transactions that have been // disconnected. We test 3 different orders: in order, one case of mixed // order and inverted order. vecptx disconnectedTxnsInOrder = {&chainedTxn[0], &chainedTxn[1], &chainedTxn[2]}; vecptx disconnectedTxnsMixedOrder = {&chainedTxn[1], &chainedTxn[2], &chainedTxn[0]}; vecptx disconnectedTxnsInvertedOrder = {&chainedTxn[2], &chainedTxn[1], &chainedTxn[0]}; // The last 2 txns simulate a chain of unconfirmed transactions in the // mempool. We test 2 different orders: in and out of order. vecptx unconfTxnsInOrder = {&chainedTxn[3], &chainedTxn[4]}; vecptx unconfTxnsOutOfOrder = {&chainedTxn[4], &chainedTxn[3]}; // Now we test all combinations of the previously defined orders for // disconnected and unconfirmed txns. The expected outcome is to have these // transactions in the correct order in queuedTx, as defined in // correctlyOrderedIds. for (auto &disconnectedTxns : {disconnectedTxnsInOrder, disconnectedTxnsMixedOrder, disconnectedTxnsInvertedOrder}) { for (auto &unconfTxns : {unconfTxnsInOrder, unconfTxnsOutOfOrder}) { // addForBlock inserts disconnectTxns in disconnectPool. They // simulate transactions that were once confirmed in a block std::vector<CTransactionRef> vtx; for (auto tx : disconnectedTxns) { vtx.push_back(MakeTransactionRef(*tx)); } DisconnectedBlockTransactions disconnectPool; disconnectPool.addForBlock(vtx); CheckDisconnectPoolOrder(disconnectPool, correctlyOrderedIds, disconnectedTxns.size()); // If the mempool is empty, importMempool doesn't change // disconnectPool CTxMemPool testPool; disconnectPool.importMempool(testPool); CheckDisconnectPoolOrder(disconnectPool, correctlyOrderedIds, disconnectedTxns.size()); { LOCK2(cs_main, testPool.cs); // Add all unconfirmed transactions in testPool for (auto tx : unconfTxns) { TestMemPoolEntryHelper entry; testPool.addUnchecked(entry.FromTx(*tx)); } } // Now we test importMempool with a non empty mempool disconnectPool.importMempool(testPool); CheckDisconnectPoolOrder(disconnectPool, correctlyOrderedIds, disconnectedTxns.size() + unconfTxns.size()); // We must clear disconnectPool to not trigger the assert in its // destructor disconnectPool.clear(); } } } inline CTransactionRef make_tx(std::vector<Amount> &&output_values, std::vector<CTransactionRef> &&inputs = std::vector<CTransactionRef>(), std::vector<uint32_t> &&input_indices = std::vector<uint32_t>()) { CMutableTransaction tx = CMutableTransaction(); tx.vin.resize(inputs.size()); tx.vout.resize(output_values.size()); for (size_t i = 0; i < inputs.size(); ++i) { tx.vin[i].prevout = COutPoint(inputs[i]->GetId(), input_indices.size() > i ? input_indices[i] : 0); } for (size_t i = 0; i < output_values.size(); ++i) { tx.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx.vout[i].nValue = output_values[i]; } return MakeTransactionRef(tx); } #define MK_OUTPUTS(amounts...) \ std::vector<Amount> { amounts } #define MK_INPUTS(txs...) \ std::vector<CTransactionRef> { txs } #define MK_INPUT_IDX(idxes...) \ std::vector<uint32_t> { idxes } BOOST_AUTO_TEST_CASE(MempoolAncestryTests) { size_t ancestors, descendants; CTxMemPool pool; LOCK2(cs_main, pool.cs); TestMemPoolEntryHelper entry; /* Base transaction */ // // [tx1] // CTransactionRef tx1 = make_tx(MK_OUTPUTS(10 * COIN)); pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tx1)); // Ancestors / descendants should be 1 / 1 (itself / itself) pool.GetTransactionAncestry(tx1->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 1ULL); BOOST_CHECK_EQUAL(descendants, 1ULL); /* Child transaction */ // // [tx1].0 <- [tx2] // CTransactionRef tx2 = make_tx(MK_OUTPUTS(495 * CENT, 5 * COIN), MK_INPUTS(tx1)); pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tx2)); // Ancestors / descendants should be: // transaction ancestors descendants // ============ =========== =========== // tx1 1 (tx1) 2 (tx1,2) // tx2 2 (tx1,2) 2 (tx1,2) pool.GetTransactionAncestry(tx1->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 1ULL); BOOST_CHECK_EQUAL(descendants, 2ULL); pool.GetTransactionAncestry(tx2->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 2ULL); BOOST_CHECK_EQUAL(descendants, 2ULL); /* Grand-child 1 */ // // [tx1].0 <- [tx2].0 <- [tx3] // CTransactionRef tx3 = make_tx(MK_OUTPUTS(290 * CENT, 200 * CENT), MK_INPUTS(tx2)); pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tx3)); // Ancestors / descendants should be: // transaction ancestors descendants // ============ =========== =========== // tx1 1 (tx1) 3 (tx1,2,3) // tx2 2 (tx1,2) 3 (tx1,2,3) // tx3 3 (tx1,2,3) 3 (tx1,2,3) pool.GetTransactionAncestry(tx1->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 1ULL); BOOST_CHECK_EQUAL(descendants, 3ULL); pool.GetTransactionAncestry(tx2->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 2ULL); BOOST_CHECK_EQUAL(descendants, 3ULL); pool.GetTransactionAncestry(tx3->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 3ULL); BOOST_CHECK_EQUAL(descendants, 3ULL); /* Grand-child 2 */ // // [tx1].0 <- [tx2].0 <- [tx3] // | // \---1 <- [tx4] // CTransactionRef tx4 = make_tx(MK_OUTPUTS(290 * CENT, 250 * CENT), MK_INPUTS(tx2), MK_INPUT_IDX(1)); pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tx4)); // Ancestors / descendants should be: // transaction ancestors descendants // ============ =========== =========== // tx1 1 (tx1) 4 (tx1,2,3,4) // tx2 2 (tx1,2) 4 (tx1,2,3,4) // tx3 3 (tx1,2,3) 4 (tx1,2,3,4) // tx4 3 (tx1,2,4) 4 (tx1,2,3,4) pool.GetTransactionAncestry(tx1->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 1ULL); BOOST_CHECK_EQUAL(descendants, 4ULL); pool.GetTransactionAncestry(tx2->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 2ULL); BOOST_CHECK_EQUAL(descendants, 4ULL); pool.GetTransactionAncestry(tx3->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 3ULL); BOOST_CHECK_EQUAL(descendants, 4ULL); pool.GetTransactionAncestry(tx4->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 3ULL); BOOST_CHECK_EQUAL(descendants, 4ULL); /* Make an alternate branch that is longer and connect it to tx3 */ // // [ty1].0 <- [ty2].0 <- [ty3].0 <- [ty4].0 <- [ty5].0 // | // [tx1].0 <- [tx2].0 <- [tx3].0 <- [ty6] --->--/ // | // \---1 <- [tx4] // CTransactionRef ty1, ty2, ty3, ty4, ty5; CTransactionRef *ty[5] = {&ty1, &ty2, &ty3, &ty4, &ty5}; Amount v = 5 * COIN; for (uint64_t i = 0; i < 5; i++) { CTransactionRef &tyi = *ty[i]; tyi = make_tx(MK_OUTPUTS(v), i > 0 ? MK_INPUTS(*ty[i - 1]) : std::vector<CTransactionRef>()); v -= 50 * CENT; pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tyi)); pool.GetTransactionAncestry(tyi->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, i + 1); BOOST_CHECK_EQUAL(descendants, i + 1); } CTransactionRef ty6 = make_tx(MK_OUTPUTS(5 * COIN), MK_INPUTS(tx3, ty5)); pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(ty6)); // Ancestors / descendants should be: // transaction ancestors descendants // ============ =================== =========== // tx1 1 (tx1) 5 (tx1,2,3,4, ty6) // tx2 2 (tx1,2) 5 (tx1,2,3,4, ty6) // tx3 3 (tx1,2,3) 5 (tx1,2,3,4, ty6) // tx4 3 (tx1,2,4) 5 (tx1,2,3,4, ty6) // ty1 1 (ty1) 6 (ty1,2,3,4,5,6) // ty2 2 (ty1,2) 6 (ty1,2,3,4,5,6) // ty3 3 (ty1,2,3) 6 (ty1,2,3,4,5,6) // ty4 4 (y1234) 6 (ty1,2,3,4,5,6) // ty5 5 (y12345) 6 (ty1,2,3,4,5,6) // ty6 9 (tx123, ty123456) 6 (ty1,2,3,4,5,6) pool.GetTransactionAncestry(tx1->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 1ULL); BOOST_CHECK_EQUAL(descendants, 5ULL); pool.GetTransactionAncestry(tx2->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 2ULL); BOOST_CHECK_EQUAL(descendants, 5ULL); pool.GetTransactionAncestry(tx3->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 3ULL); BOOST_CHECK_EQUAL(descendants, 5ULL); pool.GetTransactionAncestry(tx4->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 3ULL); BOOST_CHECK_EQUAL(descendants, 5ULL); pool.GetTransactionAncestry(ty1->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 1ULL); BOOST_CHECK_EQUAL(descendants, 6ULL); pool.GetTransactionAncestry(ty2->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 2ULL); BOOST_CHECK_EQUAL(descendants, 6ULL); pool.GetTransactionAncestry(ty3->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 3ULL); BOOST_CHECK_EQUAL(descendants, 6ULL); pool.GetTransactionAncestry(ty4->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 4ULL); BOOST_CHECK_EQUAL(descendants, 6ULL); pool.GetTransactionAncestry(ty5->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 5ULL); BOOST_CHECK_EQUAL(descendants, 6ULL); pool.GetTransactionAncestry(ty6->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 9ULL); BOOST_CHECK_EQUAL(descendants, 6ULL); /* Ancestors represented more than once ("diamond") */ // // [ta].0 <- [tb].0 -----<------- [td].0 // | | // \---1 <- [tc].0 --<--/ // CTransactionRef ta, tb, tc, td; ta = make_tx(/* output_values */ {10 * COIN}); tb = make_tx(/* output_values */ {5 * COIN, 3 * COIN}, /* inputs */ {ta}); tc = make_tx(/* output_values */ {2 * COIN}, /* inputs */ {tb}, /* input_indices */ {1}); td = make_tx(/* output_values */ {6 * COIN}, /* inputs */ {tb, tc}, /* input_indices */ {0, 0}); pool.clear(); pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(ta)); pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tb)); pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(tc)); pool.addUnchecked(entry.Fee(10000 * SATOSHI).FromTx(td)); // Ancestors / descendants should be: // transaction ancestors descendants // ============ =================== =========== // ta 1 (ta 4 (ta,tb,tc,td) // tb 2 (ta,tb) 4 (ta,tb,tc,td) // tc 3 (ta,tb,tc) 4 (ta,tb,tc,td) // td 4 (ta,tb,tc,td) 4 (ta,tb,tc,td) pool.GetTransactionAncestry(ta->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 1ULL); BOOST_CHECK_EQUAL(descendants, 4ULL); pool.GetTransactionAncestry(tb->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 2ULL); BOOST_CHECK_EQUAL(descendants, 4ULL); pool.GetTransactionAncestry(tc->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 3ULL); BOOST_CHECK_EQUAL(descendants, 4ULL); pool.GetTransactionAncestry(td->GetId(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 4ULL); BOOST_CHECK_EQUAL(descendants, 4ULL); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/setup_common.cpp b/src/test/setup_common.cpp index da0acccfd..1319119c8 100644 --- a/src/test/setup_common.cpp +++ b/src/test/setup_common.cpp @@ -1,322 +1,323 @@ // Copyright (c) 2011-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <test/setup_common.h> #include <banman.h> #include <chainparams.h> #include <config.h> #include <consensus/consensus.h> #include <consensus/validation.h> #include <crypto/sha256.h> #include <fs.h> #include <init.h> #include <key.h> #include <logging.h> #include <miner.h> #include <net.h> #include <noui.h> #include <pow.h> #include <pubkey.h> #include <random.h> #include <rpc/register.h> #include <rpc/server.h> #include <script/script_error.h> #include <script/scriptcache.h> #include <script/sigcache.h> #include <streams.h> #include <txdb.h> #include <txmempool.h> #include <util/strencodings.h> #include <util/time.h> #include <validation.h> #include <validationinterface.h> #include <memory> const std::function<std::string(const char *)> G_TRANSLATION_FUN = nullptr; FastRandomContext g_insecure_rand_ctx; std::ostream &operator<<(std::ostream &os, const uint256 &num) { os << num.ToString(); return os; } std::ostream &operator<<(std::ostream &os, const ScriptError &err) { os << ScriptErrorString(err); return os; } BasicTestingSetup::BasicTestingSetup(const std::string &chainName) : m_path_root(fs::temp_directory_path() / "test_common_" PACKAGE_NAME / strprintf("%lu_%i", static_cast<unsigned long>(GetTime()), int(InsecureRandRange(1 << 30)))) { + SetMockTime(0); fs::create_directories(m_path_root); gArgs.ForceSetArg("-datadir", m_path_root.string()); ClearDatadirCache(); SelectParams(chainName); gArgs.ForceSetArg("-printtoconsole", "0"); InitLogging(); LogInstance().StartLogging(); SHA256AutoDetect(); ECC_Start(); SetupEnvironment(); SetupNetworking(); InitSignatureCache(); InitScriptExecutionCache(); fCheckBlockIndex = true; static bool noui_connected = false; if (!noui_connected) { noui_connect(); noui_connected = true; } } BasicTestingSetup::~BasicTestingSetup() { LogInstance().DisconnectTestLogger(); fs::remove_all(m_path_root); ECC_Stop(); } TestingSetup::TestingSetup(const std::string &chainName) : BasicTestingSetup(chainName) { const Config &config = GetConfig(); const CChainParams &chainparams = config.GetChainParams(); // Ideally we'd move all the RPC tests to the functional testing framework // instead of unit tests, but for now we need these here. RPCServer rpcServer; RegisterAllRPCCommands(config, rpcServer, tableRPC); /** * RPC does not come out of the warmup state on its own. Normally, this is * handled in bitcoind's init path, but unit tests do not trigger this * codepath, so we call it explicitly as part of setup. */ std::string rpcWarmupStatus; if (RPCIsInWarmup(&rpcWarmupStatus)) { SetRPCWarmupFinished(); } // We have to run a scheduler thread to prevent ActivateBestChain // from blocking due to queue overrun. threadGroup.create_thread(std::bind(&CScheduler::serviceQueue, &scheduler)); GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); g_mempool.setSanityCheck(1.0); pblocktree.reset(new CBlockTreeDB(1 << 20, true)); pcoinsdbview.reset(new CCoinsViewDB(1 << 23, true)); pcoinsTip.reset(new CCoinsViewCache(pcoinsdbview.get())); if (!LoadGenesisBlock(chainparams)) { throw std::runtime_error("LoadGenesisBlock failed."); } { CValidationState state; if (!ActivateBestChain(config, state)) { throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", FormatStateMessage(state))); } } nScriptCheckThreads = 3; for (int i = 0; i < nScriptCheckThreads - 1; i++) { threadGroup.create_thread([i]() { return ThreadScriptCheck(i); }); } g_banman = std::make_unique<BanMan>(GetDataDir() / "banlist.dat", chainparams, nullptr, DEFAULT_MISBEHAVING_BANTIME); // Deterministic randomness for tests. g_connman = std::make_unique<CConnman>(config, 0x1337, 0x1337); } TestingSetup::~TestingSetup() { threadGroup.interrupt_all(); threadGroup.join_all(); GetMainSignals().FlushBackgroundCallbacks(); GetMainSignals().UnregisterBackgroundSignalScheduler(); g_connman.reset(); g_banman.reset(); UnloadBlockIndex(); pcoinsTip.reset(); pcoinsdbview.reset(); pblocktree.reset(); } TestChain100Setup::TestChain100Setup() : TestingSetup(CBaseChainParams::REGTEST) { // Generate a 100-block chain: coinbaseKey.MakeNewKey(true); CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG; for (int i = 0; i < COINBASE_MATURITY; i++) { std::vector<CMutableTransaction> noTxns; CBlock b = CreateAndProcessBlock(noTxns, scriptPubKey); m_coinbase_txns.push_back(b.vtx[0]); } } // // Create a new block with just given transactions, coinbase paying to // scriptPubKey, and try to add it to the current chain. // CBlock TestChain100Setup::CreateAndProcessBlock( const std::vector<CMutableTransaction> &txns, const CScript &scriptPubKey) { const Config &config = GetConfig(); std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(config, g_mempool).CreateNewBlock(scriptPubKey); CBlock &block = pblocktemplate->block; // Replace mempool-selected txns with just coinbase plus passed-in txns: block.vtx.resize(1); for (const CMutableTransaction &tx : txns) { block.vtx.push_back(MakeTransactionRef(tx)); } // Order transactions by canonical order std::sort(std::begin(block.vtx) + 1, std::end(block.vtx), [](const std::shared_ptr<const CTransaction> &txa, const std::shared_ptr<const CTransaction> &txb) -> bool { return txa->GetId() < txb->GetId(); }); // IncrementExtraNonce creates a valid coinbase and merkleRoot { LOCK(cs_main); unsigned int extraNonce = 0; IncrementExtraNonce(&block, ::ChainActive().Tip(), config.GetMaxBlockSize(), extraNonce); } const Consensus::Params ¶ms = config.GetChainParams().GetConsensus(); while (!CheckProofOfWork(block.GetHash(), block.nBits, params)) { ++block.nNonce; } std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block); ProcessNewBlock(config, shared_pblock, true, nullptr); CBlock result = block; return result; } TestChain100Setup::~TestChain100Setup() {} CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction &tx) { return FromTx(MakeTransactionRef(tx)); } CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef &tx) { return CTxMemPoolEntry(tx, nFee, nTime, nHeight, spendsCoinbase, nSigOpCount, LockPoints()); } /** * @returns a real block * (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af) with 9 * txs. */ CBlock getBlock13b8a() { CBlock block; CDataStream stream( ParseHex( "0100000090f0a9f110702f808219ebea1173056042a714bad51b916cb680000000" "0000005275289558f51c9966699404ae2294730c3c9f9bda53523ce50e9b95e558" "da2fdb261b4d4c86041b1ab1bf9309010000000100000000000000000000000000" "00000000000000000000000000000000000000ffffffff07044c86041b0146ffff" "ffff0100f2052a01000000434104e18f7afbe4721580e81e8414fc8c24d7cfacf2" "54bb5c7b949450c3e997c2dc1242487a8169507b631eb3771f2b425483fb13102c" "4eb5d858eef260fe70fbfae0ac00000000010000000196608ccbafa16abada9027" "80da4dc35dafd7af05fa0da08cf833575f8cf9e836000000004a493046022100da" "b24889213caf43ae6adc41cf1c9396c08240c199f5225acf45416330fd7dbd0221" "00fe37900e0644bf574493a07fc5edba06dbc07c311b947520c2d514bc5725dcb4" "01ffffffff0100f2052a010000001976a914f15d1921f52e4007b146dfa60f369e" "d2fc393ce288ac000000000100000001fb766c1288458c2bafcfec81e48b24d98e" "c706de6b8af7c4e3c29419bfacb56d000000008c493046022100f268ba165ce0ad" "2e6d93f089cfcd3785de5c963bb5ea6b8c1b23f1ce3e517b9f022100da7c0f21ad" "c6c401887f2bfd1922f11d76159cbc597fbd756a23dcbb00f4d7290141042b4e86" "25a96127826915a5b109852636ad0da753c9e1d5606a50480cd0c40f1f8b8d8982" "35e571fe9357d9ec842bc4bba1827daaf4de06d71844d0057707966affffffff02" "80969800000000001976a9146963907531db72d0ed1a0cfb471ccb63923446f388" "ac80d6e34c000000001976a914f0688ba1c0d1ce182c7af6741e02658c7d4dfcd3" "88ac000000000100000002c40297f730dd7b5a99567eb8d27b78758f607507c522" "92d02d4031895b52f2ff010000008b483045022100f7edfd4b0aac404e5bab4fd3" "889e0c6c41aa8d0e6fa122316f68eddd0a65013902205b09cc8b2d56e1cd1f7f2f" "afd60a129ed94504c4ac7bdc67b56fe67512658b3e014104732012cb962afa90d3" "1b25d8fb0e32c94e513ab7a17805c14ca4c3423e18b4fb5d0e676841733cb83aba" "f975845c9f6f2a8097b7d04f4908b18368d6fc2d68ecffffffffca5065ff9617cb" "cba45eb23726df6498a9b9cafed4f54cbab9d227b0035ddefb000000008a473044" "022068010362a13c7f9919fa832b2dee4e788f61f6f5d344a7c2a0da6ae7406056" "58022006d1af525b9a14a35c003b78b72bd59738cd676f845d1ff3fc25049e0100" "3614014104732012cb962afa90d31b25d8fb0e32c94e513ab7a17805c14ca4c342" "3e18b4fb5d0e676841733cb83abaf975845c9f6f2a8097b7d04f4908b18368d6fc" "2d68ecffffffff01001ec4110200000043410469ab4181eceb28985b9b4e895c13" "fa5e68d85761b7eee311db5addef76fa8621865134a221bd01f28ec9999ee3e021" "e60766e9d1f3458c115fb28650605f11c9ac000000000100000001cdaf2f758e91" "c514655e2dc50633d1e4c84989f8aa90a0dbc883f0d23ed5c2fa010000008b4830" "4502207ab51be6f12a1962ba0aaaf24a20e0b69b27a94fac5adf45aa7d2d18ffd9" "236102210086ae728b370e5329eead9accd880d0cb070aea0c96255fae6c4f1ddc" "ce1fd56e014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d78990" "4f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8" "ebbb12dcd4ffffffff02404b4c00000000001976a9142b6ba7c9d796b75eef7942" "fc9288edd37c32f5c388ac002d3101000000001976a9141befba0cdc1ad5652937" "1864d9f6cb042faa06b588ac000000000100000001b4a47603e71b61bc3326efd9" "0111bf02d2f549b067f4c4a8fa183b57a0f800cb010000008a4730440220177c37" "f9a505c3f1a1f0ce2da777c339bd8339ffa02c7cb41f0a5804f473c9230220585b" "25a2ee80eb59292e52b987dad92acb0c64eced92ed9ee105ad153cdb12d0014104" "43bd44f683467e549dae7d20d1d79cbdb6df985c6e9c029c8d0c6cb46cc1a4d3cf" "7923c5021b27f7a0b562ada113bc85d5fda5a1b41e87fe6e8802817cf69996ffff" "ffff0280651406000000001976a9145505614859643ab7b547cd7f1f5e7e2a1232" "2d3788ac00aa0271000000001976a914ea4720a7a52fc166c55ff2298e07baf70a" "e67e1b88ac00000000010000000586c62cd602d219bb60edb14a3e204de0705176" "f9022fe49a538054fb14abb49e010000008c493046022100f2bc2aba2534becbdf" "062eb993853a42bbbc282083d0daf9b4b585bd401aa8c9022100b1d7fd7ee0b956" "00db8535bbf331b19eed8d961f7a8e54159c53675d5f69df8c014104462e76fd40" "67b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c6" "9b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff03ad0e" "58ccdac3df9dc28a218bcf6f1997b0a93306faaa4b3a28ae83447b217901000000" "8b483045022100be12b2937179da88599e27bb31c3525097a07cdb52422d165b3c" "a2f2020ffcf702200971b51f853a53d644ebae9ec8f3512e442b1bcb6c315a5b49" "1d119d10624c83014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33" "d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312e" "f1c0e8ebbb12dcd4ffffffff2acfcab629bbc8685792603762c921580030ba144a" "f553d271716a95089e107b010000008b483045022100fa579a840ac258871365dd" "48cd7552f96c8eea69bd00d84f05b283a0dab311e102207e3c0ee9234814cfbb1b" "659b83671618f45abc1326b9edcc77d552a4f2a805c0014104462e76fd4067b3a0" "aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc3" "1895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffffdcdc6023bbc9" "944a658ddc588e61eacb737ddf0a3cd24f113b5a8634c517fcd2000000008b4830" "450221008d6df731df5d32267954bd7d2dda2302b74c6c2a6aa5c0ca64ecbabc1a" "f03c75022010e55c571d65da7701ae2da1956c442df81bbf076cdbac25133f99d9" "8a9ed34c014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d78990" "4f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8" "ebbb12dcd4ffffffffe15557cd5ce258f479dfd6dc6514edf6d7ed5b21fcfa4a03" "8fd69f06b83ac76e010000008b483045022023b3e0ab071eb11de2eb1cc3a67261" "b866f86bf6867d4558165f7c8c8aca2d86022100dc6e1f53a91de3efe8f6351285" "0811f26284b62f850c70ca73ed5de8771fb451014104462e76fd4067b3a0aa4207" "0082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0" "c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff01404b4c0000000000" "1976a9142b6ba7c9d796b75eef7942fc9288edd37c32f5c388ac00000000010000" "000166d7577163c932b4f9690ca6a80b6e4eb001f0a2fa9023df5595602aae96ed" "8d000000008a4730440220262b42546302dfb654a229cefc86432b89628ff259dc" "87edd1154535b16a67e102207b4634c020a97c3e7bbd0d4d19da6aa2269ad9dded" "4026e896b213d73ca4b63f014104979b82d02226b3a4597523845754d44f13639e" "3bf2df5e82c6aab2bdc79687368b01b1ab8b19875ae3c90d661a3d0a33161dab29" "934edeb36aa01976be3baf8affffffff02404b4c00000000001976a9144854e695" "a02af0aeacb823ccbc272134561e0a1688ac40420f00000000001976a914abee93" "376d6b37b5c2940655a6fcaf1c8e74237988ac0000000001000000014e3f8ef2e9" "1349a9059cb4f01e54ab2597c1387161d3da89919f7ea6acdbb371010000008c49" "304602210081f3183471a5ca22307c0800226f3ef9c353069e0773ac76bb580654" "d56aa523022100d4c56465bdc069060846f4fbf2f6b20520b2a80b08b168b31e66" "ddb9c694e240014104976c79848e18251612f8940875b2b08d06e6dc73b9840e88" "60c066b7e87432c477e9a59a453e71e6d76d5fe34058b800a098fc1740ce3012e8" "fc8a00c96af966ffffffff02c0e1e400000000001976a9144134e75a6fcb604203" "4aab5e18570cf1f844f54788ac404b4c00000000001976a9142b6ba7c9d796b75e" "ef7942fc9288edd37c32f5c388ac00000000"), SER_NETWORK, PROTOCOL_VERSION); stream >> block; return block; } diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index a9af6e041..d30543e29 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -1,492 +1,490 @@ // Copyright (c) 2012-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chain.h> #include <chainparams.h> #include <config.h> #include <consensus/validation.h> #include <interfaces/chain.h> #include <rpc/server.h> #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/rpcdump.h> #include <wallet/wallet.h> #include <test/setup_common.h> #include <wallet/test/wallet_test_fixture.h> #include <boost/test/unit_test.hpp> #include <univalue.h> #include <cstdint> #include <memory> #include <vector> BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup) static void AddKey(CWallet &wallet, const CKey &key) { LOCK(wallet.cs_wallet); wallet.AddKeyPubKey(key, key.GetPubKey()); } BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) { auto chain = interfaces::MakeChain(); // Cap last block file size, and mine new block in a new block file. CBlockIndex *oldTip = ::ChainActive().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex *newTip = ::ChainActive().Tip(); LockAnnotation lock(::cs_main); auto locked_chain = chain->lock(); // Verify ScanForWalletTransactions accommodates a null start block. { CWallet wallet(Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( BlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK(result.failed_block.IsNull()); BOOST_CHECK(result.stop_block.IsNull()); BOOST_CHECK(!result.stop_height); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), Amount::zero()); } // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { CWallet wallet(Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK(result.failed_block.IsNull()); BOOST_CHECK_EQUAL(result.stop_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.stop_height, newTip->nHeight); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); } // Prune the older block file. PruneOneBlockFile(oldTip->GetBlockPos().nFile); UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions only picks transactions in the new block // file. { CWallet wallet(Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.failed_block, oldTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.stop_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.stop_height, newTip->nHeight); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); } // Prune the remaining block file. PruneOneBlockFile(newTip->GetBlockPos().nFile); UnlinkPrunedFiles({newTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions scans no blocks. { CWallet wallet(Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.failed_block, newTip->GetBlockHash()); BOOST_CHECK(result.stop_block.IsNull()); BOOST_CHECK(!result.stop_height); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), Amount::zero()); } } BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) { auto chain = interfaces::MakeChain(); // Cap last block file size, and mine new block in a new block file. CBlockIndex *oldTip = ::ChainActive().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex *newTip = ::ChainActive().Tip(); LockAnnotation lock(::cs_main); auto locked_chain = chain->lock(); // Prune the older block file. PruneOneBlockFile(oldTip->GetBlockPos().nFile); UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify importmulti RPC returns failure for a key whose creation time is // before the missing block, and success for a key whose creation time is // after. { std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>( Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); AddWallet(wallet); UniValue keys; keys.setArray(); UniValue key; key.setObject(); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey()))); key.pushKV("timestamp", 0); key.pushKV("internal", UniValue(true)); keys.push_back(key); key.clear(); key.setObject(); CKey futureKey; futureKey.MakeNewKey(true); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey()))); key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1); key.pushKV("internal", UniValue(true)); keys.push_back(key); JSONRPCRequest request; request.params.setArray(); request.params.push_back(keys); UniValue response = importmulti(GetConfig(), request); BOOST_CHECK_EQUAL( response.write(), strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":" "\"Rescan failed for key with creation timestamp %d. " "There was an error reading a block from time %d, which " "is after or within %d seconds of key creation, and " "could contain transactions pertaining to the key. As a " "result, transactions and coins using this key may not " "appear in the wallet. This error could be caused by " "pruning or data corruption (see bitcoind log for " "details) and could be dealt with by downloading and " "rescanning the relevant blocks (see -reindex and " "-rescan options).\"}},{\"success\":true}]", 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); RemoveWallet(wallet); } } // Verify importwallet RPC starts rescan at earliest block with timestamp // greater or equal than key birthday. Previously there was a bug where // importwallet RPC would start the scan at the latest block with timestamp less // than or equal to key birthday. BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { auto chain = interfaces::MakeChain(); // Create two blocks with same timestamp to verify that importwallet rescan // will pick up both blocks, not just the first. const int64_t BLOCK_TIME = ::ChainActive().Tip()->GetBlockTimeMax() + 5; SetMockTime(BLOCK_TIME); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); // Set key birthday to block time increased by the timestamp window, so // rescan will start at the block time. const int64_t KEY_TIME = BLOCK_TIME + TIMESTAMP_WINDOW; SetMockTime(KEY_TIME); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); auto locked_chain = chain->lock(); std::string backup_file = (GetDataDir() / "wallet.backup").string(); // Import key into wallet and call dumpwallet to create backup file. { std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>( Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); LOCK(wallet->cs_wallet); wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); JSONRPCRequest request; request.params.setArray(); request.params.push_back(backup_file); AddWallet(wallet); ::dumpwallet(GetConfig(), request); RemoveWallet(wallet); } // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>( Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); JSONRPCRequest request; request.params.setArray(); request.params.push_back(backup_file); AddWallet(wallet); ::importwallet(GetConfig(), request); RemoveWallet(wallet); LOCK(wallet->cs_wallet); BOOST_CHECK_EQUAL(wallet->mapWallet.size(), 3U); BOOST_CHECK_EQUAL(m_coinbase_txns.size(), 103U); for (size_t i = 0; i < m_coinbase_txns.size(); ++i) { bool found = wallet->GetWalletTx(m_coinbase_txns[i]->GetId()); bool expected = i >= 100; BOOST_CHECK_EQUAL(found, expected); } } - - SetMockTime(0); } // Check that GetImmatureCredit() returns a newly calculated value instead of // the cached value after a MarkDirty() call. // // This is a regression test written to verify a bugfix for the immature credit // function. Similar tests probably should be written for the other credit and // debit functions. BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { auto chain = interfaces::MakeChain(); CWallet wallet(Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); CWalletTx wtx(&wallet, m_coinbase_txns.back()); auto locked_chain = chain->lock(); LOCK(wallet.cs_wallet); wtx.hashBlock = ::ChainActive().Tip()->GetBlockHash(); wtx.nIndex = 0; // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), Amount::zero()); // Invalidate the cached value, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50 * COIN); } static int64_t AddTx(CWallet &wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) { CMutableTransaction tx; tx.nLockTime = lockTime; SetMockTime(mockTime); CBlockIndex *block = nullptr; if (blockTime > 0) { LockAnnotation lock(::cs_main); auto locked_chain = wallet.chain().lock(); auto inserted = mapBlockIndex.emplace(BlockHash(GetRandHash()), new CBlockIndex); assert(inserted.second); const BlockHash &hash = inserted.first->first; block = inserted.first->second; block->nTime = blockTime; block->phashBlock = &hash; } CWalletTx wtx(&wallet, MakeTransactionRef(tx)); if (block) { wtx.SetMerkleBranch(block->GetBlockHash(), 0); } { LOCK(cs_main); wallet.AddToWallet(wtx); } LOCK(wallet.cs_wallet); return wallet.mapWallet.at(wtx.GetId()).nTimeSmart; } // Simple test to verify assignment of CWalletTx::nSmartTime value. Could be // expanded to cover more corner cases of smart time logic. BOOST_AUTO_TEST_CASE(ComputeTimeSmart) { // New transaction should use clock time if lower than block time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 100, 120), 100); // Test that updating existing transaction does not change smart time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 200, 220), 100); // New transaction should use clock time if there's no block time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 2, 300, 0), 300); // New transaction should use block time if lower than clock time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 3, 420, 400), 400); // New transaction should use latest entry time if higher than // min(block time, clock time). BOOST_CHECK_EQUAL(AddTx(m_wallet, 4, 500, 390), 400); // If there are future entries, new transaction should use time of the // newest entry that is no more than 300 seconds ahead of the clock time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 5, 50, 600), 300); // Reset mock time for other tests. SetMockTime(0); } BOOST_AUTO_TEST_CASE(LoadReceiveRequests) { CTxDestination dest = CKeyID(); LOCK(m_wallet.cs_wallet); m_wallet.AddDestData(dest, "misc", "val_misc"); m_wallet.AddDestData(dest, "rr0", "val_rr0"); m_wallet.AddDestData(dest, "rr1", "val_rr1"); auto values = m_wallet.GetDestValues("rr"); BOOST_CHECK_EQUAL(values.size(), 2U); BOOST_CHECK_EQUAL(values[0], "val_rr0"); BOOST_CHECK_EQUAL(values[1], "val_rr1"); } class ListCoinsTestingSetup : public TestChain100Setup { public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); wallet = std::make_unique<CWallet>(Params(), *m_chain, WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); CWallet::ScanResult result = wallet->ScanForWalletTransactions( ::ChainActive().Genesis()->GetBlockHash(), BlockHash(), reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(result.stop_block, ::ChainActive().Tip()->GetBlockHash()); BOOST_CHECK_EQUAL(*result.stop_height, ::ChainActive().Height()); BOOST_CHECK(result.failed_block.IsNull()); } ~ListCoinsTestingSetup() { wallet.reset(); } CWalletTx &AddTx(CRecipient recipient) { CTransactionRef tx; CReserveKey reservekey(wallet.get()); Amount fee; int changePos = -1; std::string error; CCoinControl dummy; BOOST_CHECK(wallet->CreateTransaction(*m_locked_chain, {recipient}, tx, reservekey, fee, changePos, error, dummy)); CValidationState state; BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, state)); CMutableTransaction blocktx; { LOCK(wallet->cs_wallet); blocktx = CMutableTransaction(*wallet->mapWallet.at(tx->GetId()).tx); } CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); LOCK(wallet->cs_wallet); auto it = wallet->mapWallet.find(tx->GetId()); BOOST_CHECK(it != wallet->mapWallet.end()); it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1); return it->second; } std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); // Temporary. Removed in upcoming lock cleanup std::unique_ptr<interfaces::Chain::Lock> m_locked_chain = m_chain->assumeLocked(); std::unique_ptr<CWallet> wallet; }; BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString(); // Confirm ListCoins initially returns 1 coin grouped under coinbaseKey // address. std::map<CTxDestination, std::vector<COutput>> list; { LOCK2(cs_main, wallet->cs_wallet); list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U); // Check initial balance from one mature coinbase transaction. BOOST_CHECK_EQUAL(50 * COIN, wallet->GetAvailableBalance()); // Add a transaction creating a change address, and confirm ListCoins still // returns the coin associated with the change address underneath the // coinbaseKey pubkey, even though the change address has a different // pubkey. AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); { LOCK2(cs_main, wallet->cs_wallet); list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U); // Lock both coins. Confirm number of available coins drops to 0. { LOCK2(cs_main, wallet->cs_wallet); std::vector<COutput> available; wallet->AvailableCoins(*m_locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto &group : list) { for (const auto &coin : group.second) { LOCK(wallet->cs_wallet); wallet->LockCoin(COutPoint(coin.tx->GetId(), coin.i)); } } { LOCK2(cs_main, wallet->cs_wallet); std::vector<COutput> available; wallet->AvailableCoins(*m_locked_chain, available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins // being locked. { LOCK2(cs_main, wallet->cs_wallet); list = wallet->ListCoins(*m_locked_chain); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<CKeyID>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U); } BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { auto chain = interfaces::MakeChain(); std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>( Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); CPubKey pubkey; BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false)); } BOOST_AUTO_TEST_SUITE_END()