diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -24,3 +24,6 @@ other than 1 or 2), but miners were still able to mine transactions with versions other than 1 and 2. Disallowing them by consensus allows us to use the version field for e.g. a new & scalable transaction format. + - The chained transactions limit policy will no longer be enforced by the + mempool. All the related RPC statistics and options will become irrelevant + and should no longer be relied upon. diff --git a/src/bench/chained_tx.cpp b/src/bench/chained_tx.cpp --- a/src/bench/chained_tx.cpp +++ b/src/bench/chained_tx.cpp @@ -144,6 +144,9 @@ CTxMemPool &mempool{*Assert(activeChainState.GetMempool())}; assert(mempool.size() == 0); + // test with wellington latched (faster) + const bool wellingtonBefore = mempool.wellingtonLatched.exchange(true); + bench.run([&] { LOCK(::cs_main); for (const auto &tx : chainedTxs) { @@ -156,6 +159,9 @@ mempool.clear(); }); + // restore state + mempool.wellingtonLatched = wellingtonBefore; + gArgs.ClearForcedArg("-limitdescendantcount"); gArgs.ClearForcedArg("-limitancestorcount"); gArgs.ClearForcedArg("-limitancestorsize"); @@ -189,6 +195,9 @@ CTxMemPool &mempool{*Assert(activeChainState.GetMempool())}; assert(mempool.size() == 0); + // test with wellington latched (faster) + const bool wellingtonBefore = mempool.wellingtonLatched.exchange(true); + // Build blocks TestMemPoolEntryHelper entry; entry.nFee = 1337 * SATOSHI; @@ -257,6 +266,9 @@ assert(mempool.size() == 0); }); + // restore state + mempool.wellingtonLatched = wellingtonBefore; + gArgs.ClearForcedArg("-limitdescendantcount"); gArgs.ClearForcedArg("-limitancestorcount"); gArgs.ClearForcedArg("-limitancestorsize"); @@ -274,6 +286,9 @@ Chainstate &activeChainState = chainman->ActiveChainstate(); CTxMemPool &mempool{*Assert(activeChainState.GetMempool())}; + // test with wellington latched (faster) + const bool wellingtonBefore = mempool.wellingtonLatched.exchange(true); + // Fill mempool size_t txCount = 0; for (const auto &chain : chains) { @@ -298,6 +313,9 @@ // +1 for coinbase assert(blocktemplate->block.vtx.size() == txCount + 1); }); + + // restore state + mempool.wellingtonLatched = wellingtonBefore; } static void @@ -316,6 +334,8 @@ ++i) { pools.emplace_back(); CTxMemPool &pool = pools.back(); + // test with wellington latched (faster) + pool.wellingtonLatched = true; TestMemPoolEntryHelper entry; // Fill mempool size_t txCount = 0; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -627,7 +627,8 @@ size_t &descendants, size_t *ancestorsize, Amount *ancestorfees) override { ancestors = descendants = 0; - if (!m_node.mempool) { + // After wellington this stat will no longer exist + if (!m_node.mempool || m_node.mempool->wellingtonLatched) { return; } m_node.mempool->GetTransactionAncestry(txid, ancestors, descendants, @@ -643,7 +644,8 @@ DEFAULT_DESCENDANT_LIMIT))); } bool checkChainLimits(const CTransactionRef &tx) override { - if (!m_node.mempool) { + // After wellington this limitation will no longer exist + if (!m_node.mempool || m_node.mempool->wellingtonLatched) { return true; } LockPoints lp; diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -18,167 +18,208 @@ 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 outpoints; - const size_t maxOutputs = 3; +// this test should be removed after wellington is checkpointed +BOOST_AUTO_TEST_CASE(TestPreAndPostWellingtonAccounting) { + for (const bool wellington : {false, true}) { + CTxMemPool testPool; + // test pre-wellington versus post-wellington behavior + testPool.wellingtonLatched = wellington; + LOCK2(cs_main, testPool.cs); + TestMemPoolEntryHelper entry; + CMutableTransaction parentOfAll; + + // Vector to track unspent outputs that are used to construct txs + std::vector 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.SigChecks(0).FromTx(parentOfAll)); - // 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.SigChecks(0).FromTx(parentOfAll)); + // Add some outpoints to the tracking vector + for (size_t i = 0; i < maxOutputs; i++) { + outpoints.emplace_back(COutPoint(parentOfAllId, i)); + } - // 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 totalSigChecks = 0; + + // Generate 100 transactions + for (size_t totalTransactions = 0; totalTransactions < 100; + totalTransactions++) { + CMutableTransaction tx; + + uint64_t minAncestors = std::numeric_limits::max(); + uint64_t maxAncestors = 0; + Amount minFees = MAX_MONEY; + Amount maxFees = Amount::zero(); + uint64_t minSize = std::numeric_limits::max(); + uint64_t maxSize = 0; + uint64_t minVirtualSize = std::numeric_limits::max(); + uint64_t maxVirtualSize = 0; + int64_t minSigChecks = std::numeric_limits::max(); + int64_t maxSigChecks = 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(); + minSigChecks = + std::min(minSigChecks, parent.GetSigChecksWithAncestors()); + maxSigChecks += parent.GetSigChecksWithAncestors(); + } - Amount totalFee = Amount::zero(); - size_t totalSize = CTransaction(parentOfAll).GetTotalSize(); - size_t totalVirtualSize = totalSize; - int64_t totalSigChecks = 0; - - // Generate 100 transactions - for (size_t totalTransactions = 0; totalTransactions < 100; - totalTransactions++) { - CMutableTransaction tx; - - uint64_t minAncestors = std::numeric_limits::max(); - uint64_t maxAncestors = 0; - Amount minFees = MAX_MONEY; - Amount maxFees = Amount::zero(); - uint64_t minSize = std::numeric_limits::max(); - uint64_t maxSize = 0; - uint64_t minVirtualSize = std::numeric_limits::max(); - uint64_t maxVirtualSize = 0; - int64_t minSigChecks = std::numeric_limits::max(); - int64_t maxSigChecks = 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(); - minSigChecks = - std::min(minSigChecks, parent.GetSigChecksWithAncestors()); - maxSigChecks += parent.GetSigChecksWithAncestors(); - } + // Produce random number of outputs + for (size_t output = InsecureRandRange(maxOutputs) + 1; output > 0; + output--) { + tx.vout.emplace_back(10 * SATOSHI, CScript() << OP_TRUE); + } - // 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(); - TxId curId = tx.GetId(); + // Record the outputs + for (size_t output = tx.vout.size(); output > 0; output--) { + outpoints.emplace_back(COutPoint(curId, output)); + } - // Record the outputs - for (size_t output = tx.vout.size(); output > 0; output--) { - outpoints.emplace_back(COutPoint(curId, output)); + const Amount randFee = int64_t(InsecureRandRange(300)) * SATOSHI; + const int randSigChecks = InsecureRandRange(5); + + testPool.addUnchecked( + entry.Fee(randFee).SigChecks(randSigChecks).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(), randSigChecks); + minSigChecks += randSigChecks; + maxSigChecks += randSigChecks; + + // Calculate overall values + totalFee += randFee; + totalSize += CTransaction(tx).GetTotalSize(); + totalVirtualSize += GetVirtualTransactionSize( + CTransaction(tx).GetTotalSize(), randSigChecks); + totalSigChecks += randSigChecks; + CTxMemPoolEntry parentEntry = *testPool.mapTx.find(parentOfAllId); + CTxMemPoolEntry latestEntry = *testPool.mapTx.find(curId); + + // Based on size/sigChecks 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, minSigChecks); + uint64_t maxVirtualSize_strict = + GetVirtualTransactionSize(maxSize, maxSigChecks); + uint64_t totalVirtualSize_strict = + GetVirtualTransactionSize(totalSize, totalSigChecks); + + if (!wellington) { + // 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.GetSigChecksWithAncestors() >= + minSigChecks); + BOOST_CHECK(latestEntry.GetSigChecksWithAncestors() <= + maxSigChecks); + + 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.GetSigChecksWithDescendants(), + totalSigChecks); + } else { + // with wellington latched, we stop tracking these -- they stay + // at their defaults + BOOST_CHECK_EQUAL(latestEntry.GetCountWithAncestors(), 1); + BOOST_CHECK_EQUAL(latestEntry.GetSizeWithAncestors(), + latestEntry.GetTxSize()); + BOOST_CHECK_EQUAL(latestEntry.GetVirtualSizeWithAncestors(), + latestEntry.GetTxVirtualSize()); + BOOST_CHECK_EQUAL(latestEntry.GetSigChecksWithAncestors(), + latestEntry.GetSigChecks()); + BOOST_CHECK_EQUAL(latestEntry.GetModFeesWithAncestors(), + latestEntry.GetModifiedFee()); + + BOOST_CHECK_EQUAL(parentEntry.GetCountWithDescendants(), 1); + BOOST_CHECK_EQUAL(parentEntry.GetSizeWithDescendants(), + parentEntry.GetTxSize()); + BOOST_CHECK_EQUAL(parentEntry.GetVirtualSizeWithDescendants(), + parentEntry.GetTxVirtualSize()); + BOOST_CHECK_EQUAL(parentEntry.GetModFeesWithDescendants(), + parentEntry.GetModifiedFee()); + BOOST_CHECK_EQUAL(parentEntry.GetSigChecks(), 0); + BOOST_CHECK_EQUAL(parentEntry.GetSigChecksWithDescendants(), 0); + } + // Verify that wellington activation status didn't accidentally + // change during the test. + BOOST_CHECK_EQUAL(testPool.wellingtonLatched, wellington); } - - Amount randFee = int64_t(InsecureRandRange(300)) * SATOSHI; - int randSigChecks = InsecureRandRange(5); - - testPool.addUnchecked( - entry.Fee(randFee).SigChecks(randSigChecks).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(), randSigChecks); - minSigChecks += randSigChecks; - maxSigChecks += randSigChecks; - - // Calculate overall values - totalFee += randFee; - totalSize += CTransaction(tx).GetTotalSize(); - totalVirtualSize += GetVirtualTransactionSize( - CTransaction(tx).GetTotalSize(), randSigChecks); - totalSigChecks += randSigChecks; - CTxMemPoolEntry parentEntry = *testPool.mapTx.find(parentOfAllId); - CTxMemPoolEntry latestEntry = *testPool.mapTx.find(curId); - - // Based on size/sigChecks 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, minSigChecks); - uint64_t maxVirtualSize_strict = - GetVirtualTransactionSize(maxSize, maxSigChecks); - uint64_t totalVirtualSize_strict = - GetVirtualTransactionSize(totalSize, totalSigChecks); - // 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.GetSigChecksWithAncestors() >= minSigChecks); - BOOST_CHECK(latestEntry.GetSigChecksWithAncestors() <= maxSigChecks); - - 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.GetSigChecksWithDescendants(), - totalSigChecks); } } @@ -854,6 +895,7 @@ // 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); diff --git a/src/txmempool.h b/src/txmempool.h --- a/src/txmempool.h +++ b/src/txmempool.h @@ -120,6 +120,10 @@ //! Track the height and time at which tx was final LockPoints lockPoints; + // NOTE: + // The below members will stop being updated after Wellington activation, + // and should be removed in the release after Wellington is checkpointed. + // // Information about descendants of this transaction that are in the // mempool; if we remove this transaction we must remove all of these // descendants as well. @@ -164,12 +168,13 @@ size_t DynamicMemoryUsage() const { return nUsageSize; } const LockPoints &GetLockPoints() const { return lockPoints; } - // Adjusts the descendant state. + // Adjusts the descendant state. -- To be removed after Wellington void UpdateDescendantState(int64_t modifySize, Amount modifyFee, int64_t modifyCount, int64_t modifySigChecks); - // Adjusts the ancestor state + // Adjusts the ancestor state -- To be removed after Wellington void UpdateAncestorState(int64_t modifySize, Amount modifyFee, int64_t modifyCount, int64_t modifySigChecks); + // Updates the fee delta used for mining priority score, and the // modified fees with descendants. void UpdateFeeDelta(Amount feeDelta); @@ -328,11 +333,9 @@ * (because any such children would be an orphan). So in addUnchecked(), we: * - update a new entry's setMemPoolParents to include all in-mempool parents * - update the new entry's direct parents to include the new tx as a child - * - update all ancestors of the transaction to include the new tx's size/fee * * When a transaction is removed from the mempool, we must: * - update all in-mempool parents to not track the tx in setMemPoolChildren - * - update all ancestors to not include the tx's size/fees in descendant state * - update all in-mempool children to not include it as a parent * * These happen in UpdateForRemoveFromMempool(). (Note that when removing a @@ -349,10 +352,9 @@ * * Computational limits: * - * Updating all in-mempool ancestors of a newly added transaction can be slow, - * if no bound exists on how many in-mempool ancestors there may be. - * CalculateMemPoolAncestors() takes configurable limits that are designed to - * prevent these calculations from being too CPU intensive. + * Updating all in-mempool ancestors of a newly added transaction before + * wellington activates can be slow. After wellington, no bound exists on how + * many in-mempool ancestors there may be. */ class CTxMemPool { private: @@ -447,6 +449,7 @@ using txiter = indexed_transaction_set::nth_index<0>::type::const_iterator; typedef std::set setEntries; + /// Remove after wellington activates as this will be inaccurate uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs); @@ -514,16 +517,18 @@ void check(const CCoinsViewCache &active_coins_tip, int64_t spendheight) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - // addUnchecked must updated state for all ancestors of a given transaction, - // to track size/count of descendant transactions. First version of - // addUnchecked can be used to have it call CalculateMemPoolAncestors(), and - // then invoke the second version. - // Note that addUnchecked is ONLY called from ATMP outside of tests - // and any other callers may break wallet's in-mempool tracking (due to - // lack of CValidationInterface::TransactionAddedToMempool callbacks). + // addUnchecked must update state for all parents of a given transaction, + // updating child links as necessary. + // Pre-wellington: automatically calculates setAncestors, calls + // addUnchecked(entry, setAncestors) + // Post-wellington: identical to just calling addUnchecked(entry, {}) + // These 2 overloads should be collapsed down into 1 post-wellington (just a + // single-argument version). void addUnchecked(const CTxMemPoolEntry &entry) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); - void addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAncestors) + void + addUnchecked(const CTxMemPoolEntry &entry, + const setEntries &setAncestors /* only used pre-wellington */) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason) @@ -574,9 +579,10 @@ * this set, then all in-mempool descendants must also be in the set, unless * this transaction is being removed for being in a block. Set * updateDescendants to true when removing a tx that was in a block, so that - * any in-mempool descendants have their ancestor state updated. + * any in-mempool descendants have their ancestor state updated (only + * evaluated before wellington activation). */ - void RemoveStaged(setEntries &stage, bool updateDescendants, + void RemoveStaged(const setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs); /** @@ -671,6 +677,11 @@ * When ancestors is non-zero (ie, the transaction itself is in the * mempool), ancestorsize and ancestorfees will also be set to the * appropriate values. + * + * NOTE: Since we are removing the unconf. ancestor limits after wellington, + * this function's existence is a potential DoS. It should not be + * called after wellington since it relies on calculating quadratic + * stats. */ void GetTransactionAncestry(const TxId &txid, size_t &ancestors, size_t &descendants, @@ -746,18 +757,21 @@ } private: + /** Set ancestor state for an entry */ + void UpdateEntryForAncestors(txiter it, const setEntries *setAncestors) + EXCLUSIVE_LOCKS_REQUIRED(cs); /** - * Update ancestors of hash to add/remove it as a descendant transaction. + * Update parents of `it` to add/remove it as a child transaction. */ - void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors) - EXCLUSIVE_LOCKS_REQUIRED(cs); - /** Set ancestor state for an entry */ - void UpdateEntryForAncestors(txiter it, const setEntries &setAncestors) + void UpdateParentsOf( + bool add, txiter it, + const setEntries *setAncestors = nullptr /* only used pre-wellington */) EXCLUSIVE_LOCKS_REQUIRED(cs); /** * For each transaction being removed, update ancestors and any direct * children. If updateDescendants is true, then also update in-mempool - * descendants' ancestor state. + * descendants' ancestor state. Note that ancestors are only updated before + * wellington activation. */ void UpdateForRemoveFromMempool(const setEntries &entriesToRemove, bool updateDescendants) diff --git a/src/txmempool.cpp b/src/txmempool.cpp --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -28,8 +28,14 @@ #include #include +#include + +/// Used in various places in this file to signify "no limit" for +/// CalculateMemPoolAncestors +inline constexpr uint64_t nNoLimit = std::numeric_limits::max(); // Helpers for modifying CTxMemPool::mapTx, which is a boost multi_index. +// Remove after Wellington struct update_descendant_state { update_descendant_state(int64_t _modifySize, Amount _modifyFee, int64_t _modifyCount, int64_t _modifySigChecks) @@ -100,6 +106,7 @@ return GetVirtualTransactionSize(nTxSize, sigChecks); } +// Remove after wellinggton uint64_t CTxMemPoolEntry::GetVirtualSizeWithDescendants() const { // note this is distinct from the sum of descendants' individual virtual // sizes, and may be smaller. @@ -107,6 +114,7 @@ nSigChecksWithDescendants); } +// Remove after wellinggton uint64_t CTxMemPoolEntry::GetVirtualSizeWithAncestors() const { // note this is distinct from the sum of ancestors' individual virtual // sizes, and may be smaller. @@ -115,6 +123,7 @@ } void CTxMemPoolEntry::UpdateFeeDelta(Amount newFeeDelta) { + // Remove after wellington; this stat is unused after wellington nModFeesWithDescendants += newFeeDelta - feeDelta; nModFeesWithAncestors += newFeeDelta - feeDelta; feeDelta = newFeeDelta; @@ -261,32 +270,39 @@ limitDescendantSize, errString); } -void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, - setEntries &setAncestors) { - const CTxMemPoolEntry::Parents &parents = it->GetMemPoolParentsConst(); +void CTxMemPool::UpdateParentsOf(bool add, txiter it, + const setEntries *setAncestors) { // add or remove this tx as a child of each parent - for (const CTxMemPoolEntry &parent : parents) { + for (const CTxMemPoolEntry &parent : it->GetMemPoolParentsConst()) { UpdateChild(mapTx.iterator_to(parent), it, add); } - const int64_t updateCount = (add ? 1 : -1); - const int64_t updateSize = updateCount * it->GetTxSize(); - const int64_t updateSigChecks = updateCount * it->GetSigChecks(); - const Amount updateFee = updateCount * it->GetModifiedFee(); - for (txiter ancestorIt : setAncestors) { - mapTx.modify(ancestorIt, - update_descendant_state(updateSize, updateFee, updateCount, - updateSigChecks)); + + // Remove this after wellington + if (setAncestors && !wellingtonLatched) { + const int64_t updateCount = (add ? 1 : -1); + const int64_t updateSize = updateCount * it->GetTxSize(); + const int64_t updateSigChecks = updateCount * it->GetSigChecks(); + const Amount updateFee = updateCount * it->GetModifiedFee(); + for (txiter ancestorIt : *setAncestors) { + mapTx.modify(ancestorIt, + update_descendant_state(updateSize, updateFee, + updateCount, updateSigChecks)); + } } } void CTxMemPool::UpdateEntryForAncestors(txiter it, - const setEntries &setAncestors) { - int64_t updateCount = setAncestors.size(); + const setEntries *setAncestors) { + if (!setAncestors || wellingtonLatched) { + return; + } + + int64_t updateCount = setAncestors->size(); int64_t updateSize = 0; int64_t updateSigChecks = 0; Amount updateFee = Amount::zero(); - for (txiter ancestorIt : setAncestors) { + for (txiter ancestorIt : *setAncestors) { updateSize += ancestorIt->GetTxSize(); updateFee += ancestorIt->GetModifiedFee(); updateSigChecks += ancestorIt->GetSigChecks(); @@ -304,60 +320,57 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, bool updateDescendants) { - // For each entry, walk back all ancestors and decrement size associated - // with this transaction. - const uint64_t nNoLimit = std::numeric_limits::max(); - if (updateDescendants) { - // updateDescendants should be true whenever we're not recursively - // removing a tx and all its descendants, eg when a transaction is - // confirmed in a block. - // Here we only update statistics and not data in CTxMemPool::Parents - // and CTxMemPoolEntry::Children (which we need to preserve until we're - // finished with all operations that need to traverse the mempool). - for (txiter removeIt : entriesToRemove) { - setEntries setDescendants; - CalculateDescendants(removeIt, setDescendants); - setDescendants.erase(removeIt); // don't update state for self - int64_t modifySize = -int64_t(removeIt->GetTxSize()); - Amount modifyFee = -1 * removeIt->GetModifiedFee(); - int modifySigChecks = -removeIt->GetSigChecks(); - for (txiter dit : setDescendants) { - mapTx.modify(dit, update_ancestor_state(modifySize, modifyFee, - -1, modifySigChecks)); + if (!wellingtonLatched) { + // remove this branch after wellington + // slow quadratic branch, only for pre-activation compatibility + + // For each entry, walk back all ancestors and decrement size associated + // with this transaction. + if (updateDescendants) { + // updateDescendants should be true whenever we're not recursively + // removing a tx and all its descendants, eg when a transaction is + // confirmed in a block. + // Here we only update statistics and not data in + // CTxMemPool::Parents and CTxMemPoolEntry::Children (which we need + // to preserve until we're finished with all operations that need to + // traverse the mempool). + for (txiter removeIt : entriesToRemove) { + setEntries setDescendants; + CalculateDescendants(removeIt, setDescendants); + setDescendants.erase(removeIt); // don't update state for self + int64_t modifySize = -int64_t(removeIt->GetTxSize()); + Amount modifyFee = -1 * removeIt->GetModifiedFee(); + int modifySigChecks = -removeIt->GetSigChecks(); + for (txiter dit : setDescendants) { + mapTx.modify(dit, + update_ancestor_state(modifySize, modifyFee, + -1, modifySigChecks)); + } } } - } - for (txiter removeIt : entriesToRemove) { - setEntries setAncestors; - const CTxMemPoolEntry &entry = *removeIt; - std::string dummy; - // Since this is a tx that is already in the mempool, we can call CMPA - // with fSearchForParents = false. If the mempool is in a consistent - // state, then using true or false should both be correct, though false - // should be a bit faster. - // However, if we happen to be in the middle of processing a reorg, then - // the mempool can be in an inconsistent state. In this case, the set - // of ancestors reachable via GetMemPoolParents()/GetMemPoolChildren() - // will be the same as the set of ancestors whose packages include this - // transaction, because when we add a new transaction to the mempool in - // addUnchecked(), we assume it has no children, and in the case of a - // reorg where that assumption is false, the in-mempool children aren't - // linked to the in-block tx's until UpdateTransactionsFromBlock() is - // called. - // So if we're being called during a reorg, ie before - // UpdateTransactionsFromBlock() has been called, then - // GetMemPoolParents()/GetMemPoolChildren() will differ from the set of - // mempool parents we'd calculate by searching, and it's important that - // we use the cached notion of ancestor transactions as the set of - // things to update for removal. - CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, - nNoLimit, nNoLimit, dummy, false); - // Note that UpdateAncestorsOf severs the child links that point to - // removeIt in the entries for the parents of removeIt. - UpdateAncestorsOf(false, removeIt, setAncestors); + for (txiter removeIt : entriesToRemove) { + setEntries setAncestors; + const CTxMemPoolEntry &entry = *removeIt; + std::string dummy; + // Since this is a tx that is already in the mempool, we can call + // CMPA with fSearchForParents = false. If the mempool is in a + // consistent state, then using true or false should both be + // correct, though false should be a bit faster. + CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, + nNoLimit, nNoLimit, dummy, false); + // Note that UpdateParentsOf severs the child links that point to + // removeIt in the entries for the parents of removeIt. + UpdateParentsOf(false, removeIt, &setAncestors); + } + } else { + for (txiter removeIt : entriesToRemove) { + // Note that UpdateParentsOf severs the child links that point to + // removeIt in the entries for the parents of removeIt. + UpdateParentsOf(false, removeIt); + } } - // After updating all the ancestor sizes, we can now sever the link between + // After updating all the parent links, we can now sever the link between // each transaction being removed and any mempool children (ie, update // CTxMemPoolEntry::m_parents for each direct child of a transaction being // removed). @@ -412,7 +425,7 @@ } void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entryIn, - setEntries &setAncestors) { + const setEntries &setAncestors) { CTxMemPoolEntry entry{entryIn}; // get a guaranteed unique id (in case tests re-use the same object) entry.SetEntryId(nextEntryId++); @@ -453,8 +466,10 @@ for (const auto &pit : GetIterSet(setParentTransactions)) { UpdateParent(newit, pit, true); } - UpdateAncestorsOf(true, newit, setAncestors); - UpdateEntryForAncestors(newit, setAncestors); + + const setEntries *pSetEntries = wellingtonLatched ? nullptr : &setAncestors; + UpdateParentsOf(true, newit, pSetEntries); + UpdateEntryForAncestors(newit, pSetEntries); nTransactionsUpdated++; totalTxSize += entry.GetTxSize(); @@ -689,25 +704,29 @@ it->GetMemPoolParentsConst().begin(), comp)); // Verify ancestor state is correct. setEntries setAncestors; - uint64_t nNoLimit = std::numeric_limits::max(); std::string dummy; - CalculateMemPoolAncestors(*it, setAncestors, nNoLimit, nNoLimit, - nNoLimit, nNoLimit, dummy); - uint64_t nCountCheck = setAncestors.size() + 1; - uint64_t nSizeCheck = it->GetTxSize(); - Amount nFeesCheck = it->GetModifiedFee(); - int64_t nSigChecksCheck = it->GetSigChecks(); - - for (txiter ancestorIt : setAncestors) { - nSizeCheck += ancestorIt->GetTxSize(); - nFeesCheck += ancestorIt->GetModifiedFee(); - nSigChecksCheck += ancestorIt->GetSigChecks(); - } - assert(it->GetCountWithAncestors() == nCountCheck); - assert(it->GetSizeWithAncestors() == nSizeCheck); - assert(it->GetSigChecksWithAncestors() == nSigChecksCheck); - assert(it->GetModFeesWithAncestors() == nFeesCheck); + const bool ok = CalculateMemPoolAncestors( + *it, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy); + assert(ok); + + if (!wellingtonLatched) { + uint64_t nCountCheck = setAncestors.size() + 1; + uint64_t nSizeCheck = it->GetTxSize(); + Amount nFeesCheck = it->GetModifiedFee(); + int64_t nSigChecksCheck = it->GetSigChecks(); + + for (txiter ancestorIt : setAncestors) { + nSizeCheck += ancestorIt->GetTxSize(); + nFeesCheck += ancestorIt->GetModifiedFee(); + nSigChecksCheck += ancestorIt->GetSigChecks(); + } + + assert(it->GetCountWithAncestors() == nCountCheck); + assert(it->GetSizeWithAncestors() == nSizeCheck); + assert(it->GetSigChecksWithAncestors() == nSigChecksCheck); + assert(it->GetModFeesWithAncestors() == nFeesCheck); + } // all ancestors should have entryId < this tx's entryId for (const auto &ancestor : setAncestors) { @@ -733,12 +752,15 @@ assert(setChildrenCheck.size() == it->GetMemPoolChildrenConst().size()); assert(std::equal(setChildrenCheck.begin(), setChildrenCheck.end(), it->GetMemPoolChildrenConst().begin(), comp)); - // Also check to make sure size is greater than sum with immediate - // children. Just a sanity check, not definitive that this calc is - // correct... - assert(it->GetSizeWithDescendants() >= child_sizes + it->GetTxSize()); - assert(it->GetSigChecksWithDescendants() >= - child_sigChecks + it->GetSigChecks()); + if (!wellingtonLatched) { + // Also check to make sure size is greater than sum with immediate + // children. Just a sanity check, not definitive that this calc is + // correct... + assert(it->GetSizeWithDescendants() >= + child_sizes + it->GetTxSize()); + assert(it->GetSigChecksWithDescendants() >= + child_sigChecks + it->GetSigChecks()); + } // Not used. CheckTxInputs() should always pass TxValidationState dummy_state; @@ -852,24 +874,26 @@ if (it != mapTx.end()) { mapTx.modify( it, [&delta](CTxMemPoolEntry &e) { e.UpdateFeeDelta(delta); }); - // Now update all ancestors' modified fees with descendants - setEntries setAncestors; - uint64_t nNoLimit = std::numeric_limits::max(); - std::string dummy; - CalculateMemPoolAncestors(*it, setAncestors, nNoLimit, nNoLimit, - nNoLimit, nNoLimit, dummy, false); - for (txiter ancestorIt : setAncestors) { - mapTx.modify(ancestorIt, - update_descendant_state(0, nFeeDelta, 0, 0)); - } + // Remove after wellington + if (!wellingtonLatched) { + // Now update all ancestors' modified fees with descendants + setEntries setAncestors; + std::string dummy; + CalculateMemPoolAncestors(*it, setAncestors, nNoLimit, nNoLimit, + nNoLimit, nNoLimit, dummy, false); + for (txiter ancestorIt : setAncestors) { + mapTx.modify(ancestorIt, + update_descendant_state(0, nFeeDelta, 0, 0)); + } - // Now update all descendants' modified fees with ancestors - setEntries setDescendants; - CalculateDescendants(it, setDescendants); - setDescendants.erase(it); - for (txiter descendantIt : setDescendants) { - mapTx.modify(descendantIt, - update_ancestor_state(0, nFeeDelta, 0, 0)); + // Now update all descendants' modified fees with ancestors + setEntries setDescendants; + CalculateDescendants(it, setDescendants); + setDescendants.erase(it); + for (txiter descendantIt : setDescendants) { + mapTx.modify(descendantIt, + update_ancestor_state(0, nFeeDelta, 0, 0)); + } } ++nTransactionsUpdated; } @@ -984,7 +1008,7 @@ } } -void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants, +void CTxMemPool::RemoveStaged(const setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason) { AssertLockHeld(cs); UpdateForRemoveFromMempool(stage, updateDescendants); @@ -1031,10 +1055,11 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry) { setEntries setAncestors; - uint64_t nNoLimit = std::numeric_limits::max(); - std::string dummy; - CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, - nNoLimit, dummy); + if (!wellingtonLatched) { + std::string dummy; + CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, + nNoLimit, nNoLimit, dummy); + } return addUnchecked(entry, setAncestors); } @@ -1134,6 +1159,7 @@ } } +/// Remove after wellington; after wellington activates this will be inaccurate uint64_t CTxMemPool::CalculateDescendantMaximum(txiter entry) const { // find parent with highest descendant count std::vector candidates; diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -697,14 +697,19 @@ strprintf("%d < %d", ws.m_modified_fees, mempoolRejectFee)); } - // Calculate in-mempool ancestors, up to a limit. - std::string errString; - if (!m_pool.CalculateMemPoolAncestors( - *entry, ws.m_ancestors, m_limit_ancestors, m_limit_ancestor_size, - m_limit_descendants, m_limit_descendant_size, errString)) { - return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, - "too-long-mempool-chain", errString); + // Remove after wellington + if (!m_pool.wellingtonLatched) { + // Calculate in-mempool ancestors, up to a limit. + std::string errString; + if (!m_pool.CalculateMemPoolAncestors( + *entry, ws.m_ancestors, m_limit_ancestors, + m_limit_ancestor_size, m_limit_descendants, + m_limit_descendant_size, errString)) { + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, + "too-long-mempool-chain", errString); + } } + return true; } diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h --- a/src/wallet/coinselection.h +++ b/src/wallet/coinselection.h @@ -82,8 +82,8 @@ bool m_from_me{true}; Amount m_value = Amount::zero(); int m_depth{999}; - size_t m_ancestors{0}; - size_t m_descendants{0}; + size_t m_ancestors{0}; ///< deprecated after wellington activation + size_t m_descendants{0}; ///< deprecated after wellington activation Amount effective_value = Amount::zero(); Amount fee = Amount::zero(); Amount long_term_fee = Amount::zero(); diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -311,9 +311,13 @@ CTxDestination dst; CInputCoin input_coin = output.GetInputCoin(); + // deprecated -- after wellington activation these 2 stats should + // always just be 0 since these stat becomes irrelevant at that + // point size_t ancestors, descendants; wallet.chain().getTransactionAncestry(output.tx->GetId(), ancestors, descendants); + if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst)) { @@ -502,6 +506,8 @@ } } + // Note: after wellington the ancestor stats will always be 0, since this + // limitation becomes irrelevant. size_t max_ancestors{0}; size_t max_descendants{0}; wallet.chain().getPackageLimits(max_ancestors, max_descendants); @@ -942,6 +948,7 @@ return false; } + // After wellington this option will no longer exist if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits diff --git a/test/functional/abc_mempool_chainedtx.py b/test/functional/abc_mempool_chainedtx.py new file mode 100755 --- /dev/null +++ b/test/functional/abc_mempool_chainedtx.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 The Bitcoin developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the chained txs limit.""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_greater_than, + assert_raises_rpc_error, +) +from test_framework.wallet import create_raw_chain + +MAX_CHAINED_TX = 5 +WELLINGTON_ACTIVATION_TIME = 2000000000 + + +class ChainedTxTest(BitcoinTestFramework): + + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + self.extra_args = [[ + f"-replayprotectionactivationtime={WELLINGTON_ACTIVATION_TIME + 1000}", + f"-wellingtonactivationtime={WELLINGTON_ACTIVATION_TIME}", + f"-limitancestorcount={MAX_CHAINED_TX}", + f"-limitdescendantcount={MAX_CHAINED_TX + 1}", + ]] + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + node = self.nodes[0] + node.setmocktime(WELLINGTON_ACTIVATION_TIME - 1000) + + self.privkeys = [node.get_deterministic_priv_key().key] + self.address = node.get_deterministic_priv_key().address + self.coins = [] + # The last 100 coinbase transactions are premature + for b in self.generatetoaddress(node, 102, self.address)[:2]: + coinbase = node.getblock(blockhash=b, verbosity=2)["tx"][0] + self.coins.append({ + "txid": coinbase["txid"], + "amount": coinbase["vout"][0]["value"], + "scriptPubKey": coinbase["vout"][0]["scriptPubKey"], + }) + + self.log.info("Before Wellington, the chained-tx limit applies") + + assert_greater_than( + WELLINGTON_ACTIVATION_TIME, + node.getblockchaininfo()['mediantime']) + + chain_hex, _ = create_raw_chain( + node, self.coins.pop(), self.address, self.privkeys, chain_length=MAX_CHAINED_TX + 1) + + for i in range(MAX_CHAINED_TX): + txid = node.sendrawtransaction(chain_hex[i]) + mempool = node.getrawmempool() + assert_equal(len(mempool), i + 1) + assert txid in mempool + + assert_raises_rpc_error( + -26, + f"too-long-mempool-chain, too many unconfirmed ancestors [limit: {MAX_CHAINED_TX}]", + node.sendrawtransaction, + chain_hex[-1]) + + self.log.info("Activate Wellington") + + node.setmocktime(WELLINGTON_ACTIVATION_TIME) + self.generate(node, 6) + assert_equal( + node.getblockchaininfo()['mediantime'], + WELLINGTON_ACTIVATION_TIME) + + self.log.info( + "After Wellington, the chained-tx limit no longer applies") + + chain_hex, _ = create_raw_chain( + node, self.coins.pop(), self.address, self.privkeys, chain_length=MAX_CHAINED_TX * 2) + + for i in range(MAX_CHAINED_TX * 2): + txid = node.sendrawtransaction(chain_hex[i]) + mempool = node.getrawmempool() + assert_equal(len(mempool), i + 1) + assert txid in mempool + + self.log.info("Upon reorg the mempool policy is maintained") + + node.invalidateblock(node.getbestblockhash()) + assert_greater_than( + WELLINGTON_ACTIVATION_TIME, + node.getblockchaininfo()['mediantime']) + + # Mempool size should be limited again + assert_equal(len(node.getrawmempool()), MAX_CHAINED_TX * 2) + + # Mine an activation block to clear the mempool + self.generate(node, 1) + assert_equal(len(node.getrawmempool()), 0) + + # Reorg that block, and make sure all the txs are added back to the + # mempool + node.invalidateblock(node.getbestblockhash()) + assert_greater_than( + WELLINGTON_ACTIVATION_TIME, + node.getblockchaininfo()['mediantime']) + assert_equal(len(node.getrawmempool()), MAX_CHAINED_TX * 2) + + +if __name__ == '__main__': + ChainedTxTest().main() diff --git a/test/functional/mempool_package_limits.py b/test/functional/mempool_package_limits.py --- a/test/functional/mempool_package_limits.py +++ b/test/functional/mempool_package_limits.py @@ -20,11 +20,18 @@ make_chain, ) +FAR_IN_THE_FUTURE = 2000000000 + class MempoolPackageLimitsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True + self.extra_args = [[ + # The packages mempool limits are no longer applied after wellington + # activation. + f'-wellingtonactivationtime={FAR_IN_THE_FUTURE}', + ]] def run_test(self): self.log.info("Generate blocks to create UTXOs") diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -21,13 +21,20 @@ # custom limits for node1 MAX_ANCESTORS_CUSTOM = 5 +FAR_IN_THE_FUTURE = 2000000000 + class MempoolPackagesTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 common_params = [ "-maxorphantx=1000", - "-deprecatedrpc=mempool_ancestors_descendants"] + "-deprecatedrpc=mempool_ancestors_descendants", + # This test tests mempool ancestor chain limits, which are no longer + # enforced after wellington, so we need to force wellington to + # activate in the distant future + f"-wellingtonactivationtime={FAR_IN_THE_FUTURE}", + ] self.extra_args = [ common_params, common_params + ["-limitancestorcount={}".format(MAX_ANCESTORS_CUSTOM)]] diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -12,6 +12,8 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error +FAR_IN_THE_FUTURE = 2000000000 + def create_transactions(node, address, amt, fees): # Create and sign raw transactions from node to address for amt. @@ -50,8 +52,11 @@ self.setup_clean_chain = True self.extra_args = [ # Limit mempool descendants as a hack to have wallet txs rejected - # from the mempool - ['-limitdescendantcount=3'], + # from the mempool. This will no longer work after wellington, so + # move the activation in the future for this test. + ['-limitdescendantcount=3', + f'-wellingtonactivationtime={FAR_IN_THE_FUTURE}', + ], [], ] # whitelist peers to speed up tx relay / mempool sync diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -16,13 +16,25 @@ ) from test_framework.wallet_util import test_address +FAR_IN_THE_FUTURE = 2000000000 + class WalletTest(BitcoinTestFramework): + + WELLINGTON_FAR_FUTURE = f"-wellingtonactivationtime={int(9e9)}" + def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True self.extra_args = [ - ["-acceptnonstdtxn=1", "-whitelist=noban@127.0.0.1"], + [ + "-acceptnonstdtxn=1", + "-whitelist=noban@127.0.0.1", + # This test tests mempool ancestor chain limits, which are no + # longer enforced after wellington, so we need to force + # wellington to activate in the distant future + f"-wellingtonactivationtime={FAR_IN_THE_FUTURE}", + ], ] * self.num_nodes self.supports_cli = False