diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -4,4 +4,7 @@ -This is a maintenance release with no user-visible change. +This release includes the following features and fixes: + - The `ancestorcount`, `ancestorsize` and `ancestorfees` fields from the + `listunspent` RPC have been removed. They were deprecated since the v0.27.0 + and are no longer computed since the May 15th, 2023 eCash network upgrade. 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 @@ -131,22 +131,12 @@ static void benchATMP(const Config &config, node::NodeContext &node, benchmark::Bench &bench, const std::vector chainedTxs) { - // ATMP uses gArgs, so we oblige - gArgs.ForceSetArg("-limitdescendantcount", ToString(chainedTxs.size())); - gArgs.ForceSetArg("-limitancestorcount", ToString(chainedTxs.size())); - gArgs.ForceSetArg("-limitancestorsize", ToString(chainedTxs.size() * 1000)); - gArgs.ForceSetArg("-limitdescendantsize", - ToString(chainedTxs.size() * 1000)); - auto chainman = Assert(node.chainman.get()); Chainstate &activeChainState = chainman->ActiveChainstate(); 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) { @@ -158,14 +148,6 @@ } mempool.clear(); }); - - // restore state - mempool.wellingtonLatched = wellingtonBefore; - - gArgs.ClearForcedArg("-limitdescendantcount"); - gArgs.ClearForcedArg("-limitancestorcount"); - gArgs.ClearForcedArg("-limitancestorsize"); - gArgs.ClearForcedArg("-limitdescendantsize"); } /// Run benchmark that reorganizes blocks with one-input-one-output transaction @@ -195,9 +177,6 @@ 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; @@ -225,14 +204,6 @@ } CBlockIndex *mostWorkTip = activeChainState.m_chain.Tip(); - // `AcceptToMemoryPool` is used during re-org, so we need to ajust its - // limits. - gArgs.ForceSetArg("-limitdescendantcount", ToString(chainSizePerBlock)); - gArgs.ForceSetArg("-limitancestorcount", ToString(chainSizePerBlock)); - gArgs.ForceSetArg("-limitancestorsize", ToString(chainSizePerBlock * 1000)); - gArgs.ForceSetArg("-limitdescendantsize", - ToString(chainSizePerBlock * 1000)); - bench.run([&] { BlockValidationState state; @@ -265,14 +236,6 @@ assert(activeChainState.m_chain.Tip() == mostWorkTip); assert(mempool.size() == 0); }); - - // restore state - mempool.wellingtonLatched = wellingtonBefore; - - gArgs.ClearForcedArg("-limitdescendantcount"); - gArgs.ClearForcedArg("-limitancestorcount"); - gArgs.ClearForcedArg("-limitancestorsize"); - gArgs.ClearForcedArg("-limitdescendantsize"); } static void @@ -286,9 +249,6 @@ 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) { @@ -313,9 +273,6 @@ // +1 for coinbase assert(blocktemplate->block.vtx.size() == txCount + 1); }); - - // restore state - mempool.wellingtonLatched = wellingtonBefore; } static void @@ -334,8 +291,6 @@ ++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/interfaces/chain.h b/src/interfaces/chain.h --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -205,22 +205,12 @@ const Amount &max_tx_fee, bool relay, std::string &err_string) = 0; - //! Calculate mempool ancestor and descendant counts for the given - //! transaction. - virtual void getTransactionAncestry(const TxId &txid, size_t &ancestors, - size_t &descendants, - size_t *ancestorsize = nullptr, - Amount *ancestorfees = nullptr) = 0; - //! Get the node's package limits. //! Currently only returns the ancestor and descendant count limits, but //! could be enhanced to return more policy settings. virtual void getPackageLimits(size_t &limit_ancestor_count, size_t &limit_descendant_count) = 0; - //! Check if transaction will pass the mempool's chain limits. - virtual bool checkChainLimits(const CTransactionRef &tx) = 0; - //! Estimate fee virtual CFeeRate estimateFee() const = 0; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -623,17 +623,6 @@ // to know about. return err == TransactionError::OK; } - void getTransactionAncestry(const TxId &txid, size_t &ancestors, - size_t &descendants, size_t *ancestorsize, - Amount *ancestorfees) override { - ancestors = descendants = 0; - // After wellington this stat will no longer exist - if (!m_node.mempool || m_node.mempool->wellingtonLatched) { - return; - } - m_node.mempool->GetTransactionAncestry(txid, ancestors, descendants, - ancestorsize, ancestorfees); - } void getPackageLimits(size_t &limit_ancestor_count, size_t &limit_descendant_count) override { limit_ancestor_count = size_t( @@ -643,33 +632,6 @@ 1, gArgs.GetIntArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT))); } - bool checkChainLimits(const CTransactionRef &tx) override { - // After wellington this limitation will no longer exist - if (!m_node.mempool || m_node.mempool->wellingtonLatched) { - return true; - } - LockPoints lp; - CTxMemPoolEntry entry(tx, Amount(), 0, 0, false, 0, lp); - CTxMemPool::setEntries ancestors; - auto limit_ancestor_count = - gArgs.GetIntArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); - auto limit_ancestor_size = - gArgs.GetIntArg("-limitancestorsize", - DEFAULT_ANCESTOR_SIZE_LIMIT) * - 1000; - auto limit_descendant_count = gArgs.GetIntArg( - "-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); - auto limit_descendant_size = - gArgs.GetIntArg("-limitdescendantsize", - DEFAULT_DESCENDANT_SIZE_LIMIT) * - 1000; - std::string unused_error_string; - LOCK(m_node.mempool->cs); - return m_node.mempool->CalculateMemPoolAncestors( - entry, ancestors, limit_ancestor_count, limit_ancestor_size, - limit_descendant_count, limit_descendant_size, - unused_error_string); - } CFeeRate estimateFee() const override { if (!m_node.mempool) { return {}; diff --git a/src/test/activation_tests.cpp b/src/test/activation_tests.cpp --- a/src/test/activation_tests.cpp +++ b/src/test/activation_tests.cpp @@ -77,54 +77,4 @@ BOOST_CHECK(IsWellingtonEnabled(params, activation + 1)); } -BOOST_FIXTURE_TEST_CASE(wellington_latch, RegTestingSetup) { - const Consensus::Params ¶ms = Params().GetConsensus(); - const auto activation = gArgs.GetIntArg("-wellingtonactivationtime", - params.wellingtonActivationTime); - SetMockTime(activation - 1000000); - - const Config &config = GetConfig(); - auto &mempool = *Assert(m_node.mempool); - auto &chainman = *Assert(m_node.chainman); - auto &activeChainstate = chainman.ActiveChainstate(); - - std::array blocks; - for (size_t i = 1; i < blocks.size() - 1; ++i) { - blocks[i].pprev = &blocks[i - 1]; - } - // Hack: we need to call MineBlock to avoid null pointer dereference in - // ATMP. Let's make sure it's our tip so SetMTP works as expected. - MineBlock(config, m_node, CScript() << OP_TRUE); - blocks[11] = *activeChainstate.m_chain.Tip(); - blocks[11].pprev = &blocks[10]; - activeChainstate.m_chain.SetTip(&blocks[11]); - - auto submitTxToMemPool = [&config, &activeChainstate]() { - LOCK(cs_main); - return AcceptToMemoryPool(config, activeChainstate, - MakeTransactionRef(), - /*accept_time=*/0, - /*bypass_limits=*/false); - }; - - BOOST_CHECK(!IsWellingtonEnabled(params, &blocks.back())); - submitTxToMemPool(); - BOOST_CHECK(!mempool.wellingtonLatched); - - // Activate Wellington - SetMTP(blocks, activation); - BOOST_CHECK(IsWellingtonEnabled(params, &blocks.back())); - - // Upon first tx submission, the flag is latched - BOOST_CHECK(!mempool.wellingtonLatched); - submitTxToMemPool(); - BOOST_CHECK(mempool.wellingtonLatched); - - // Simulate a reorg and check the flag did latch - SetMTP(blocks, activation - 1); - BOOST_CHECK(mempool.wellingtonLatched); - submitTxToMemPool(); - BOOST_CHECK(mempool.wellingtonLatched); -} - BOOST_AUTO_TEST_SUITE_END() 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,212 +18,6 @@ BOOST_FIXTURE_TEST_SUITE(mempool_tests, TestingSetup) -// 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)); - - // 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. - - const 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); - } - - TxId curId = tx.GetId(); - - // 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; - const CTxMemPoolEntry &parentEntry = - *testPool.mapTx.find(parentOfAllId); - const 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); - } - } -} - static constexpr auto REMOVAL_REASON_DUMMY = MemPoolRemovalReason::REPLACED; BOOST_AUTO_TEST_CASE(MempoolRemoveTest) { @@ -706,211 +500,6 @@ return MakeTransactionRef(tx); } -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(/* output_values */ {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(/* output_values */ {495 * CENT, 5 * COIN}, /* 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(/* output_values */ {290 * CENT, 200 * CENT}, - /* 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(/* output_values */ {290 * CENT, 250 * CENT}, - /* inputs */ {tx2}, /* input_indices */ {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(/* output_values */ {v}, - /* inputs */ i > 0 - ? std::vector{*ty[i - 1]} - : std::vector{}); - 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(/* output_values */ {5 * COIN}, /* 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_CASE(GetModifiedFeeRateTest) { CMutableTransaction tx = CMutableTransaction(); tx.vin.resize(1); diff --git a/src/txmempool.h b/src/txmempool.h --- a/src/txmempool.h +++ b/src/txmempool.h @@ -444,10 +444,6 @@ 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); - private: void UpdateParent(txiter entry, txiter parent, bool add) EXCLUSIVE_LOCKS_REQUIRED(cs); @@ -482,15 +478,6 @@ indirectmap mapNextTx GUARDED_BY(cs); std::map mapDeltas GUARDED_BY(cs); - /** - * Wellington activation latch. This is latched permanently to true in - * AcceptToMemoryPool the first time a tx arrives and - * IsWellingtonActivated() returns true. This should be removed after - * wellington is checkpointed and its mempool-accept/relay rules become - * retroactively permanent. - */ - std::atomic wellingtonLatched{false}; - /** * Create a new CTxMemPool. * Sanity checks will be off by default for performance, because otherwise @@ -666,23 +653,6 @@ std::chrono::seconds age) EXCLUSIVE_LOCKS_REQUIRED(cs, ::cs_main); - /** - * Calculate the ancestor and descendant count for the given transaction. - * The counts include the transaction itself. - * 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, - size_t *ancestorsize = nullptr, - Amount *ancestorfees = nullptr) const; - /** @returns true if the mempool is fully loaded */ bool IsLoaded() const; @@ -758,10 +728,7 @@ /** * Update parents of `it` to add/remove it as a child transaction. */ - void UpdateParentsOf( - bool add, txiter it, - const setEntries *setAncestors = nullptr /* only used pre-wellington */) - EXCLUSIVE_LOCKS_REQUIRED(cs); + void UpdateParentsOf(bool add, txiter it) EXCLUSIVE_LOCKS_REQUIRED(cs); /** * For each transaction being removed, update ancestors and any direct * children. If updateDescendants is true, then also update in-mempool diff --git a/src/txmempool.cpp b/src/txmempool.cpp --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -269,45 +269,11 @@ limitDescendantSize, errString); } -void CTxMemPool::UpdateParentsOf(bool add, txiter it, - const setEntries *setAncestors) { +void CTxMemPool::UpdateParentsOf(bool add, txiter it) { // add or remove this tx as a child of each parent for (const CTxMemPoolEntry &parent : it->GetMemPoolParentsConst()) { UpdateChild(mapTx.iterator_to(parent), it, add); } - - // 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) { - 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) { - updateSize += ancestorIt->GetTxSize(); - updateFee += ancestorIt->GetModifiedFee(); - updateSigChecks += ancestorIt->GetSigChecks(); - } - mapTx.modify(it, update_ancestor_state(updateSize, updateFee, updateCount, - updateSigChecks)); } void CTxMemPool::UpdateChildrenForRemoval(txiter it) { @@ -319,56 +285,12 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, bool updateDescendants) { - 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. - 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); - } + 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 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 @@ -466,9 +388,7 @@ UpdateParent(newit, pit, true); } - const setEntries *pSetEntries = wellingtonLatched ? nullptr : &setAncestors; - UpdateParentsOf(true, newit, pSetEntries); - UpdateEntryForAncestors(newit, pSetEntries); + UpdateParentsOf(true, newit); nTransactionsUpdated++; totalTxSize += entry.GetTxSize(); @@ -710,24 +630,6 @@ entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy); assert(ok); - if (!wellingtonLatched) { - uint64_t nCountCheck = setAncestors.size() + 1; - uint64_t nSizeCheck = entry.GetTxSize(); - Amount nFeesCheck = entry.GetModifiedFee(); - int64_t nSigChecksCheck = entry.GetSigChecks(); - - for (txiter ancestorIt : setAncestors) { - nSizeCheck += ancestorIt->GetTxSize(); - nFeesCheck += ancestorIt->GetModifiedFee(); - nSigChecksCheck += ancestorIt->GetSigChecks(); - } - - assert(entry.GetCountWithAncestors() == nCountCheck); - assert(entry.GetSizeWithAncestors() == nSizeCheck); - assert(entry.GetSigChecksWithAncestors() == nSigChecksCheck); - assert(entry.GetModFeesWithAncestors() == nFeesCheck); - } - // all ancestors should have entryId < this tx's entryId for (const auto &ancestor : setAncestors) { assert(ancestor->GetEntryId() < entry.GetEntryId()); @@ -753,15 +655,6 @@ entry.GetMemPoolChildrenConst().size()); assert(std::equal(setChildrenCheck.begin(), setChildrenCheck.end(), entry.GetMemPoolChildrenConst().begin(), comp)); - 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(entry.GetSizeWithDescendants() >= - child_sizes + entry.GetTxSize()); - assert(entry.GetSigChecksWithDescendants() >= - child_sigChecks + entry.GetSigChecks()); - } // Not used. CheckTxInputs() should always pass TxValidationState dummy_state; @@ -873,27 +766,6 @@ if (it != mapTx.end()) { mapTx.modify( it, [&delta](CTxMemPoolEntry &e) { e.UpdateFeeDelta(delta); }); - // 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)); - } - } ++nTransactionsUpdated; } } @@ -1054,11 +926,6 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry) { setEntries setAncestors; - if (!wellingtonLatched) { - std::string dummy; - CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, - nNoLimit, nNoLimit, dummy); - } return addUnchecked(entry, setAncestors); } @@ -1158,51 +1025,6 @@ } } -/// 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; - setEntries counted; - candidates.push_back(entry); - uint64_t maximum = 0; - while (candidates.size()) { - txiter candidate = candidates.back(); - candidates.pop_back(); - if (!counted.insert(candidate).second) { - continue; - } - const CTxMemPoolEntry::Parents &parents = - candidate->GetMemPoolParentsConst(); - if (parents.size() == 0) { - maximum = std::max(maximum, candidate->GetCountWithDescendants()); - } else { - for (const CTxMemPoolEntry &i : parents) { - candidates.push_back(mapTx.iterator_to(i)); - } - } - } - return maximum; -} - -void CTxMemPool::GetTransactionAncestry(const TxId &txid, size_t &ancestors, - size_t &descendants, - size_t *const ancestorsize, - Amount *const ancestorfees) const { - LOCK(cs); - auto it = mapTx.find(txid); - ancestors = descendants = 0; - if (it != mapTx.end()) { - ancestors = it->GetCountWithAncestors(); - if (ancestorsize) { - *ancestorsize = it->GetSizeWithAncestors(); - } - if (ancestorfees) { - *ancestorfees = it->GetModFeesWithAncestors(); - } - descendants = CalculateDescendantMaximum(it); - } -} - bool CTxMemPool::IsLoaded() const { LOCK(cs); return m_is_loaded; diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -704,19 +704,6 @@ strprintf("%d < %d", ws.m_modified_fees, mempoolRejectFee)); } - // 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; } @@ -917,15 +904,6 @@ args.m_config.GetChainParams().GetConsensus(); const CBlockIndex *tip = m_active_chainstate.m_chain.Tip(); - // After wellington we will stop computing the ancestors and descendant - // limits, and stop enforcing them. Because the mempool has no idea what the - // current tip is (and it should not), we will use an activation latch so it - // knows when the statistics can be skipped. This also makes an hypothetical - // reorg event easier to handle. - // Note that |= operator is not defined for atomic bool ! - m_pool.wellingtonLatched = - m_pool.wellingtonLatched || IsWellingtonEnabled(consensusParams, tip); - Workspace ws(ptx, GetNextBlockScriptFlags(consensusParams, tip)); // Perform the inexpensive checks first and avoid hashing and signature diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3251,22 +3251,6 @@ entry.pushKV("scriptPubKey", HexStr(scriptPubKey)); entry.pushKV("amount", out.tx->tx->vout[out.i].nValue); entry.pushKV("confirmations", out.nDepth); - - // Deprecated in v0.27.0 - if (!out.nDepth && - IsDeprecatedRPCEnabled(gArgs, - "mempool_ancestors_descendants")) { - size_t ancestor_count, descendant_count, ancestor_size; - Amount ancestor_fees; - pwallet->chain().getTransactionAncestry( - out.tx->GetId(), ancestor_count, descendant_count, - &ancestor_size, &ancestor_fees); - if (ancestor_count) { - entry.pushKV("ancestorcount", uint64_t(ancestor_count)); - entry.pushKV("ancestorsize", uint64_t(ancestor_size)); - entry.pushKV("ancestorfees", ancestor_fees); - } - } entry.pushKV("spendable", out.fSpendable); entry.pushKV("solvable", out.fSolvable); if (out.fSolvable) { diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -314,9 +314,7 @@ // 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); + size_t ancestors = 0, descendants = 0; if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, @@ -948,16 +946,6 @@ 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 - if (!wallet.chain().checkChainLimits(tx)) { - error = _("Transaction has too long of a mempool chain"); - return false; - } - } - // Before we return success, we assume any change key will be used to // prevent accidental re-use. reservedest.KeepDestination(); diff --git a/test/functional/abc_mempool_chainedtx.py b/test/functional/abc_mempool_chainedtx.py --- a/test/functional/abc_mempool_chainedtx.py +++ b/test/functional/abc_mempool_chainedtx.py @@ -5,36 +5,22 @@ """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.util import assert_equal from test_framework.wallet import create_raw_chain -MAX_CHAINED_TX = 5 -WELLINGTON_ACTIVATION_TIME = 2000000000 +LEGACY_MAX_CHAINED_TX = 5 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 @@ -50,10 +36,8 @@ } ) - self.log.info("Before Wellington, the chained-tx limit applies") - - assert_greater_than( - WELLINGTON_ACTIVATION_TIME, node.getblockchaininfo()["mediantime"] + self.log.info( + "Since Wellington activation, the chained-tx limit no longer applies" ) chain_hex, _ = create_raw_chain( @@ -61,69 +45,15 @@ self.coins.pop(), self.address, self.privkeys, - chain_length=MAX_CHAINED_TX + 1, + chain_length=LEGACY_MAX_CHAINED_TX * 2, ) - for i in range(MAX_CHAINED_TX): + for i in range(LEGACY_MAX_CHAINED_TX * 2): txid = node.sendrawtransaction(chain_hex[i]) mempool = node.getrawmempool() assert_equal(len(mempool), i + 1) assert txid in mempool - assert_raises_rpc_error( - -26, - ( - "too-long-mempool-chain, too many unconfirmed ancestors [limit:" - f" {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 deleted file mode 100755 --- a/test/functional/mempool_package_limits.py +++ /dev/null @@ -1,676 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2021 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test logic for limiting mempool and package ancestors/descendants.""" - -from decimal import Decimal - -from test_framework.address import ADDRESS_ECREG_P2SH_OP_TRUE, SCRIPTSIG_OP_TRUE -from test_framework.messages import XEC, CTransaction, FromHex, ToHex -from test_framework.test_framework import BitcoinTestFramework -from test_framework.txtools import pad_tx -from test_framework.util import assert_equal -from test_framework.wallet import ( - bulk_transaction, - create_child_with_parents, - 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") - node = self.nodes[0] - 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, 200, self.address)[:100]: - 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.test_chain_limits() - self.test_desc_count_limits() - self.test_desc_count_limits_2() - self.test_anc_count_limits() - self.test_anc_count_limits_2() - self.test_anc_count_limits_bushy() - - # The node will accept our (nonstandard) extra large OP_RETURN outputs - self.restart_node(0, extra_args=["-acceptnonstdtxn=1"]) - self.test_anc_size_limits() - self.test_desc_size_limits() - - def test_chain_limits_helper(self, mempool_count, package_count): - node = self.nodes[0] - assert_equal(0, node.getmempoolinfo()["size"]) - first_coin = self.coins.pop() - spk = None - txid = first_coin["txid"] - chain_hex = [] - chain_txns = [] - value = first_coin["amount"] - - for i in range(mempool_count + package_count): - (tx, txhex, value, spk) = make_chain( - node, self.address, self.privkeys, txid, value, 0, spk - ) - txid = tx.get_id() - if i < mempool_count: - node.sendrawtransaction(txhex) - else: - chain_hex.append(txhex) - chain_txns.append(tx) - testres_too_long = node.testmempoolaccept(rawtxs=chain_hex) - for txres in testres_too_long: - assert_equal(txres["package-error"], "package-mempool-limits") - - # Clear mempool and check that the package passes now - self.generate(node, 1) - assert all( - res["allowed"] is True for res in node.testmempoolaccept(rawtxs=chain_hex) - ) - - def test_chain_limits(self): - """Create chains from mempool and package transactions that are longer than 50, - but only if both in-mempool and in-package transactions are considered together. - This checks that both mempool and in-package transactions are taken into account - when calculating ancestors/descendant limits. - """ - self.log.info( - "Check that in-package ancestors count for mempool ancestor limits" - ) - - self.test_chain_limits_helper(mempool_count=49, package_count=2) - self.test_chain_limits_helper(mempool_count=2, package_count=49) - self.test_chain_limits_helper(mempool_count=26, package_count=26) - - def test_desc_count_limits(self): - """Create an 'A' shaped package with 49 transactions in the mempool and 2 in the package: - M1 - ^ ^ - M2a M2b - . . - . . - M25a M25b - ^ ^ - Pa Pb - The top ancestor in the package exceeds descendant limits but only if the in-mempool and in-package - descendants are all considered together (49 including in-mempool descendants and 51 including both - package transactions). - """ - node = self.nodes[0] - assert_equal(0, node.getmempoolinfo()["size"]) - self.log.info( - "Check that in-mempool and in-package descendants are calculated properly" - " in packages" - ) - # Top parent in mempool, M1 - first_coin = self.coins.pop() - # Deduct reasonable fee and make 2 outputs - parent_value = (first_coin["amount"] - Decimal("200.00")) / 2 - inputs = [{"txid": first_coin["txid"], "vout": 0}] - outputs = [ - {self.address: parent_value}, - {ADDRESS_ECREG_P2SH_OP_TRUE: parent_value}, - ] - rawtx = node.createrawtransaction(inputs, outputs) - - parent_signed = node.signrawtransactionwithkey( - hexstring=rawtx, privkeys=self.privkeys - ) - assert parent_signed["complete"] - parent_tx = FromHex(CTransaction(), parent_signed["hex"]) - parent_txid = parent_tx.rehash() - node.sendrawtransaction(parent_signed["hex"]) - - package_hex = [] - - # Chain A - spk = parent_tx.vout[0].scriptPubKey.hex() - value = parent_value - txid = parent_txid - for i in range(25): - (tx, txhex, value, spk) = make_chain( - node, self.address, self.privkeys, txid, value, 0, spk - ) - txid = tx.get_id() - if i < 24: - # M2a... M25a - node.sendrawtransaction(txhex) - else: - # Pa - package_hex.append(txhex) - - # Chain B - value = parent_value - Decimal("100.00") - rawtx_b = node.createrawtransaction( - [{"txid": parent_txid, "vout": 1}], {self.address: value} - ) - # M2b - tx_child_b = FromHex(CTransaction(), rawtx_b) - tx_child_b.vin[0].scriptSig = SCRIPTSIG_OP_TRUE - pad_tx(tx_child_b) - tx_child_b_hex = ToHex(tx_child_b) - node.sendrawtransaction(tx_child_b_hex) - spk = tx_child_b.vout[0].scriptPubKey.hex() - txid = tx_child_b.rehash() - for i in range(24): - (tx, txhex, value, spk) = make_chain( - node, self.address, self.privkeys, txid, value, 0, spk - ) - txid = tx.get_id() - if i < 23: - # M3b... M25b - node.sendrawtransaction(txhex) - else: - # Pb - package_hex.append(txhex) - - assert_equal(49, node.getmempoolinfo()["size"]) - assert_equal(2, len(package_hex)) - testres_too_long = node.testmempoolaccept(rawtxs=package_hex) - for txres in testres_too_long: - assert_equal(txres["package-error"], "package-mempool-limits") - - # Clear mempool and check that the package passes now - self.generate(node, 1) - assert all( - res["allowed"] is True for res in node.testmempoolaccept(rawtxs=package_hex) - ) - - def test_desc_count_limits_2(self): - """Create a Package with 49 transactions in mempool and 2 transactions - in package: - M1 - ^ ^ - M2 ^ - . ^ - . ^ - . ^ - M49 ^ - ^ - P1 - ^ - P2 - P1 has M1 as a mempool ancestor, P2 has no in-mempool ancestors, but - when combined P2 has M1 as an ancestor and M1 exceeds descendant_limits - (48 in-mempool descendants + 2 in-package descendants, a total of 51 - including itself). - """ - - node = self.nodes[0] - package_hex = [] - # M1 - first_coin_a = self.coins.pop() - # Deduct reasonable fee and make 2 outputs - parent_value = (first_coin_a["amount"] - Decimal("200.0")) / 2 - inputs = [{"txid": first_coin_a["txid"], "vout": 0}] - outputs = [ - {self.address: parent_value}, - {ADDRESS_ECREG_P2SH_OP_TRUE: parent_value}, - ] - rawtx = node.createrawtransaction(inputs, outputs) - - parent_signed = node.signrawtransactionwithkey( - hexstring=rawtx, privkeys=self.privkeys - ) - assert parent_signed["complete"] - parent_tx = FromHex(CTransaction(), parent_signed["hex"]) - pad_tx(parent_tx) - parent_txid = parent_tx.rehash() - node.sendrawtransaction(parent_signed["hex"]) - - # Chain M2...M49 - spk = parent_tx.vout[0].scriptPubKey.hex() - value = parent_value - txid = parent_txid - for _ in range(48): - (tx, txhex, value, spk) = make_chain( - node, self.address, self.privkeys, txid, value, 0, spk - ) - pad_tx(tx) - txid = tx.hash - node.sendrawtransaction(txhex) - - # P1 - value_p1 = parent_value - Decimal("100") - rawtx_p1 = node.createrawtransaction( - [{"txid": parent_txid, "vout": 1}], [{self.address: value_p1}] - ) - tx_child_p1 = FromHex(CTransaction(), rawtx_p1) - tx_child_p1.vin[0].scriptSig = SCRIPTSIG_OP_TRUE - pad_tx(tx_child_p1) - tx_child_p1_hex = tx_child_p1.serialize().hex() - package_hex.append(tx_child_p1_hex) - tx_child_p1_spk = tx_child_p1.vout[0].scriptPubKey.hex() - - # P2 - (_, tx_child_p2_hex, _, _) = make_chain( - node, - self.address, - self.privkeys, - tx_child_p1.hash, - value_p1, - 0, - tx_child_p1_spk, - ) - package_hex.append(tx_child_p2_hex) - - assert_equal(49, node.getmempoolinfo()["size"]) - assert_equal(2, len(package_hex)) - testres = node.testmempoolaccept(rawtxs=package_hex) - assert_equal(len(testres), len(package_hex)) - for txres in testres: - assert_equal(txres["package-error"], "package-mempool-limits") - - # Clear mempool and check that the package passes now - self.generate(node, 1) - assert all(res["allowed"] for res in node.testmempoolaccept(rawtxs=package_hex)) - - def test_anc_count_limits(self): - """Create a 'V' shaped chain with 49 transactions in the mempool and 3 in the package: - M1a - ^ M1b - M2a ^ - . M2b - . . - . . - M25a M24b - ^ ^ - Pa Pb - ^ ^ - Pc - The lowest descendant, Pc, exceeds ancestor limits, but only if the in-mempool - and in-package ancestors are all considered together. - """ - node = self.nodes[0] - assert_equal(0, node.getmempoolinfo()["size"]) - package_hex = [] - parents_tx = [] - values = [] - scripts = [] - - self.log.info( - "Check that in-mempool and in-package ancestors are calculated " - "properly in packages" - ) - - # Two chains of 26 & 25 transactions - for chain_length in [26, 25]: - spk = None - top_coin = self.coins.pop() - txid = top_coin["txid"] - value = top_coin["amount"] - for i in range(chain_length): - (tx, txhex, value, spk) = make_chain( - node, self.address, self.privkeys, txid, value, 0, spk - ) - txid = tx.get_id() - if i < chain_length - 1: - node.sendrawtransaction(txhex) - else: - # Save the last transaction for the package - package_hex.append(txhex) - parents_tx.append(tx) - scripts.append(spk) - values.append(value) - - # Child Pc - child_hex = create_child_with_parents( - node, self.address, self.privkeys, parents_tx, values, scripts - ) - package_hex.append(child_hex) - - assert_equal(49, node.getmempoolinfo()["size"]) - assert_equal(3, len(package_hex)) - testres_too_long = node.testmempoolaccept(rawtxs=package_hex) - for txres in testres_too_long: - assert_equal(txres["package-error"], "package-mempool-limits") - - # Clear mempool and check that the package passes now - self.generate(node, 1) - assert all( - res["allowed"] is True for res in node.testmempoolaccept(rawtxs=package_hex) - ) - - def test_anc_count_limits_2(self): - """Create a 'Y' shaped chain with 49 transactions in the mempool and 2 in the package: - M1a - ^ M1b - M2a ^ - . M2b - . . - . . - M25a M24b - ^ ^ - Pc - ^ - Pd - The lowest descendant, Pc, exceeds ancestor limits, but only if the in-mempool - and in-package ancestors are all considered together. - """ - node = self.nodes[0] - assert_equal(0, node.getmempoolinfo()["size"]) - parents_tx = [] - values = [] - scripts = [] - - self.log.info( - "Check that in-mempool and in-package ancestors are calculated properly in" - " packages" - ) - # Two chains of 25 & 24 transactions - for chain_length in [25, 24]: - spk = None - top_coin = self.coins.pop() - txid = top_coin["txid"] - value = top_coin["amount"] - for i in range(chain_length): - (tx, txhex, value, spk) = make_chain( - node, self.address, self.privkeys, txid, value, 0, spk - ) - txid = tx.get_id() - node.sendrawtransaction(txhex) - if i == chain_length - 1: - # last 2 transactions will be the parents of Pc - parents_tx.append(tx) - values.append(value) - scripts.append(spk) - - # Child Pc - pc_hex = create_child_with_parents( - node, self.address, self.privkeys, parents_tx, values, scripts - ) - pc_tx = FromHex(CTransaction(), pc_hex) - pc_value = sum(values) - Decimal("100.00") - pc_spk = pc_tx.vout[0].scriptPubKey.hex() - - # Child Pd - (_, pd_hex, _, _) = make_chain( - node, self.address, self.privkeys, pc_tx.get_id(), pc_value, 0, pc_spk - ) - - assert_equal(49, node.getmempoolinfo()["size"]) - testres_too_long = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) - for txres in testres_too_long: - assert_equal(txres["package-error"], "package-mempool-limits") - - # Clear mempool and check that the package passes now - self.generate(node, 1) - assert all( - res["allowed"] is True - for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) - ) - - def test_anc_count_limits_bushy(self): - """Create a tree with 45 transactions in the mempool and 6 in the package: - M1...M9 M10...M18 M19...M27 M28...M36 M37...M45 - ^ ^ ^ ^ ^ (each with 9 parents) - P0 P1 P2 P3 P4 - ^ ^ ^ ^ ^ (5 parents) - PC - Where M(9i+1)...M+(9i+9) are the parents of Pi and P0, P1, P2, P3, and P4 are the parents of PC. - P0... P4 individually only have 9 parents each, and PC has no in-mempool parents. But - combined, PC has 50 in-mempool and in-package parents. - """ - node = self.nodes[0] - assert_equal(0, node.getmempoolinfo()["size"]) - package_hex = [] - parent_txns = [] - parent_values = [] - scripts = [] - # Make package transactions P0 ... P4 - for _ in range(5): - gp_tx = [] - gp_values = [] - gp_scripts = [] - # Make mempool transactions M(9i+1)...M(9i+9) - for _ in range(9): - parent_coin = self.coins.pop() - value = parent_coin["amount"] - txid = parent_coin["txid"] - (tx, txhex, value, spk) = make_chain( - node, self.address, self.privkeys, txid, value - ) - gp_tx.append(tx) - gp_values.append(value) - gp_scripts.append(spk) - node.sendrawtransaction(txhex) - # Package transaction Pi - pi_hex = create_child_with_parents( - node, self.address, self.privkeys, gp_tx, gp_values, gp_scripts - ) - package_hex.append(pi_hex) - pi_tx = FromHex(CTransaction(), pi_hex) - parent_txns.append(pi_tx) - parent_values.append(Decimal(pi_tx.vout[0].nValue) / XEC) - scripts.append(pi_tx.vout[0].scriptPubKey.hex()) - # Package transaction PC - package_hex.append( - create_child_with_parents( - node, self.address, self.privkeys, parent_txns, parent_values, scripts - ) - ) - - assert_equal(45, node.getmempoolinfo()["size"]) - assert_equal(6, len(package_hex)) - testres = node.testmempoolaccept(rawtxs=package_hex) - for txres in testres: - assert_equal(txres["package-error"], "package-mempool-limits") - - # Clear mempool and check that the package passes now - self.generate(node, 1) - assert all( - res["allowed"] is True for res in node.testmempoolaccept(rawtxs=package_hex) - ) - - def test_anc_size_limits(self): - """Test Case with 2 independent transactions in the mempool and a parent + child in the - package, where the package parent is the child of both mempool transactions (30KB each): - A B - ^ ^ - C - ^ - D - The lowest descendant, D, exceeds ancestor size limits, but only if the in-mempool - and in-package ancestors are all considered together. - """ - node = self.nodes[0] - - assert_equal(0, node.getmempoolinfo()["size"]) - parents_tx = [] - values = [] - scripts = [] - target_size = 30_000 - # 10 sats/B - high_fee = Decimal("3000.00") - self.log.info( - "Check that in-mempool and in-package ancestor size limits are calculated" - " properly in packages" - ) - # Mempool transactions A and B - for _ in range(2): - spk = None - top_coin = self.coins.pop() - txid = top_coin["txid"] - value = top_coin["amount"] - (tx, _, _, _) = make_chain( - node, self.address, self.privkeys, txid, value, 0, spk, high_fee - ) - bulked_tx = bulk_transaction(tx, node, target_size, self.privkeys) - node.sendrawtransaction(ToHex(bulked_tx)) - parents_tx.append(bulked_tx) - values.append(Decimal(bulked_tx.vout[0].nValue) / XEC) - scripts.append(bulked_tx.vout[0].scriptPubKey.hex()) - - # Package transaction C - small_pc_hex = create_child_with_parents( - node, self.address, self.privkeys, parents_tx, values, scripts, high_fee - ) - pc_tx = bulk_transaction( - FromHex(CTransaction(), small_pc_hex), node, target_size, self.privkeys - ) - pc_value = Decimal(pc_tx.vout[0].nValue) / XEC - pc_spk = pc_tx.vout[0].scriptPubKey.hex() - pc_hex = ToHex(pc_tx) - - # Package transaction D - (small_pd, _, val, spk) = make_chain( - node, - self.address, - self.privkeys, - pc_tx.rehash(), - pc_value, - 0, - pc_spk, - high_fee, - ) - prevtxs = [ - { - "txid": pc_tx.get_id(), - "vout": 0, - "scriptPubKey": spk, - "amount": pc_value, - } - ] - pd_tx = bulk_transaction(small_pd, node, target_size, self.privkeys, prevtxs) - pd_hex = ToHex(pd_tx) - - assert_equal(2, node.getmempoolinfo()["size"]) - testres_too_heavy = node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) - for txres in testres_too_heavy: - assert_equal(txres["package-error"], "package-mempool-limits") - - # Clear mempool and check that the package passes now - self.generate(node, 1) - assert all( - res["allowed"] is True - for res in node.testmempoolaccept(rawtxs=[pc_hex, pd_hex]) - ) - - def test_desc_size_limits(self): - """Create 3 mempool transactions and 2 package transactions (25KB each): - Ma - ^ ^ - Mb Mc - ^ ^ - Pd Pe - The top ancestor in the package exceeds descendant size limits but only if the in-mempool - and in-package descendants are all considered together. - """ - node = self.nodes[0] - assert_equal(0, node.getmempoolinfo()["size"]) - target_size = 21_000 - # 10 sats/vB - high_fee = Decimal("2100.00") - self.log.info( - "Check that in-mempool and in-package descendant sizes are calculated" - " properly in packages" - ) - # Top parent in mempool, Ma - first_coin = self.coins.pop() - # Deduct fee and make 2 outputs - parent_value = (first_coin["amount"] - high_fee) / 2 - inputs = [{"txid": first_coin["txid"], "vout": 0}] - outputs = [ - {self.address: parent_value}, - {ADDRESS_ECREG_P2SH_OP_TRUE: parent_value}, - ] - rawtx = node.createrawtransaction(inputs, outputs) - parent_tx = bulk_transaction( - FromHex(CTransaction(), rawtx), node, target_size, self.privkeys - ) - node.sendrawtransaction(ToHex(parent_tx)) - - package_hex = [] - # Two legs (left and right) - for j in range(2): - # Mempool transaction (Mb and Mc) - spk = parent_tx.vout[j].scriptPubKey.hex() - value = Decimal(parent_tx.vout[j].nValue) / XEC - txid = parent_tx.get_id() - prevtxs = [ - { - "txid": txid, - "vout": j, - "scriptPubKey": spk, - "amount": value, - } - ] - if j == 0: - # normal key - (tx_small, _, _, _) = make_chain( - node, self.address, self.privkeys, txid, value, j, spk, high_fee - ) - mempool_tx = bulk_transaction( - tx_small, node, target_size, self.privkeys, prevtxs - ) - - else: - # OP_TRUE - inputs = [{"txid": txid, "vout": 1}] - outputs = {self.address: value - high_fee} - small_tx = FromHex( - CTransaction(), node.createrawtransaction(inputs, outputs) - ) - mempool_tx = bulk_transaction( - small_tx, node, target_size, None, prevtxs - ) - node.sendrawtransaction(ToHex(mempool_tx)) - - # Package transaction (Pd and Pe) - spk = mempool_tx.vout[0].scriptPubKey.hex() - value = Decimal(mempool_tx.vout[0].nValue) / XEC - txid = mempool_tx.get_id() - (tx_small, _, _, _) = make_chain( - node, self.address, self.privkeys, txid, value, 0, spk, high_fee - ) - prevtxs = [ - { - "txid": txid, - "vout": 0, - "scriptPubKey": spk, - "amount": value, - } - ] - package_tx = bulk_transaction( - tx_small, node, target_size, self.privkeys, prevtxs - ) - package_hex.append(ToHex(package_tx)) - - assert_equal(3, node.getmempoolinfo()["size"]) - assert_equal(2, len(package_hex)) - testres_too_heavy = node.testmempoolaccept(rawtxs=package_hex) - for txres in testres_too_heavy: - assert_equal(txres["package-error"], "package-mempool-limits") - - # Clear mempool and check that the package passes now - self.generate(node, 1) - assert all( - res["allowed"] is True for res in node.testmempoolaccept(rawtxs=package_hex) - ) - - -if __name__ == "__main__": - MempoolPackageLimitsTest().main() 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 @@ -8,33 +8,15 @@ from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round +from test_framework.util import assert_equal, satoshi_round -# default limits MAX_ANCESTORS = 50 -MAX_DESCENDANTS = 50 - -# 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", - # 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 + [f"-limitancestorcount={MAX_ANCESTORS_CUSTOM}"], - ] + self.extra_args = [["-maxorphantx=1000"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -62,17 +44,11 @@ self.generate(self.nodes[0], 101) utxo = self.nodes[0].listunspent(10) txid = utxo[0]["txid"] - vout = utxo[0]["vout"] value = utxo[0]["amount"] - assert "ancestorcount" not in utxo[0] - assert "ancestorsize" not in utxo[0] - assert "ancestorfees" not in utxo[0] fee = Decimal("100") # MAX_ANCESTORS transactions off a confirmed tx should be fine chain = [] - ancestor_size = 0 - ancestor_fees = Decimal(0) for i in range(MAX_ANCESTORS): (txid, sent_value) = self.chain_transaction( self.nodes[0], txid, 0, value, fee, 1 @@ -80,39 +56,22 @@ value = sent_value chain.append(txid) - # Check that listunspent ancestor{count, size, fees} yield the - # correct results - wallet_unspent = self.nodes[0].listunspent(minconf=0) - this_unspent = next( - utxo_info for utxo_info in wallet_unspent if utxo_info["txid"] == txid - ) - assert_equal(this_unspent["ancestorcount"], i + 1) - ancestor_size += self.nodes[0].getrawtransaction(txid=txid, verbose=True)[ - "size" - ] - assert_equal(this_unspent["ancestorsize"], ancestor_size) - ancestor_fees -= self.nodes[0].gettransaction(txid=txid)["fee"] - assert_equal(this_unspent["ancestorfees"], ancestor_fees) - # Wait until mempool transactions have passed initial broadcast # (sent inv and received getdata) # Otherwise, getrawmempool may be inconsistent with getmempoolentry if # unbroadcast changes in between peer_inv_store.wait_for_broadcast(chain) - # Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor - # count and fees should look correct + # Check mempool has MAX_ANCESTORS transactions in it mempool = self.nodes[0].getrawmempool(True) assert_equal(len(mempool), MAX_ANCESTORS) descendant_count = 1 descendant_fees = 0 descendant_size = 0 - assert_equal(ancestor_size, sum([mempool[tx]["size"] for tx in mempool])) + ancestor_size = sum([mempool[tx]["size"] for tx in mempool]) ancestor_count = MAX_ANCESTORS - assert_equal( - ancestor_fees, sum([mempool[tx]["fees"]["base"] for tx in mempool]) - ) + ancestor_fees = sum([mempool[tx]["fees"]["base"] for tx in mempool]) descendants = [] ancestors = list(chain) @@ -120,19 +79,11 @@ # Check that getmempoolentry is consistent with getrawmempool entry = self.nodes[0].getmempoolentry(x) assert_equal(entry, mempool[x]) - # Check that the descendant calculations are correct - assert_equal(mempool[x]["descendantcount"], descendant_count) descendant_fees += mempool[x]["fees"]["base"] assert_equal(mempool[x]["fees"]["modified"], mempool[x]["fees"]["base"]) - assert_equal(mempool[x]["fees"]["descendant"], descendant_fees) descendant_size += mempool[x]["size"] - assert_equal(mempool[x]["descendantsize"], descendant_size) descendant_count += 1 - # Check that ancestor calculations are correct - assert_equal(mempool[x]["ancestorcount"], ancestor_count) - assert_equal(mempool[x]["fees"]["ancestor"], ancestor_fees) - assert_equal(mempool[x]["ancestorsize"], ancestor_size) ancestor_size -= mempool[x]["size"] ancestor_fees -= mempool[x]["fees"]["base"] ancestor_count -= 1 @@ -151,10 +102,6 @@ self.nodes[0].getmempooldescendants(x, True).items() ): assert_equal(dinfo["depends"], [chain[chain.index(descendant) - 1]]) - if dinfo["descendantcount"] > 1: - assert_equal(dinfo["spentby"], [chain[chain.index(descendant) + 1]]) - else: - assert_equal(dinfo["spentby"], []) descendants.append(x) # Check that getmempoolancestors is correct @@ -166,10 +113,10 @@ # Check that getmempoolancestors verbose output is correct for ancestor, ainfo in self.nodes[0].getmempoolancestors(x, True).items(): assert_equal(ainfo["spentby"], [chain[chain.index(ancestor) + 1]]) - if ainfo["ancestorcount"] > 1: - assert_equal(ainfo["depends"], [chain[chain.index(ancestor) - 1]]) - else: - assert_equal(ainfo["depends"], []) + + # Check we covered all the ancestors + assert_equal(ancestor_size, 0) + assert_equal(ancestor_fees, 0) # Check that getmempoolancestors/getmempooldescendants correctly handle # verbose=true @@ -185,45 +132,6 @@ assert_equal(mempool[x], v_descendants[x]) assert chain[0] not in v_descendants.keys() - # Check that ancestor modified fees includes fee deltas from - # prioritisetransaction - self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=1000) - mempool = self.nodes[0].getrawmempool(True) - ancestor_fees = 0 - for x in chain: - ancestor_fees += mempool[x]["fees"]["base"] - assert_equal( - mempool[x]["fees"]["ancestor"], ancestor_fees + Decimal("10.00") - ) - - # Undo the prioritisetransaction for later tests - self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=-1000) - - # Check that descendant modified fees includes fee deltas from - # prioritisetransaction - self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=1000) - mempool = self.nodes[0].getrawmempool(True) - - descendant_fees = 0 - for x in reversed(chain): - descendant_fees += mempool[x]["fees"]["base"] - assert_equal( - mempool[x]["fees"]["descendant"], descendant_fees + Decimal("10.00") - ) - - # Adding one more transaction on to the chain should fail. - assert_raises_rpc_error( - -26, - "too-long-mempool-chain", - self.chain_transaction, - self.nodes[0], - txid, - vout, - value, - fee, - 1, - ) - # Check that prioritising a tx before it's added to the mempool works # First clear the mempool by mining a block. self.generate(self.nodes[0], 1) @@ -239,25 +147,17 @@ # modified fee mempool = self.nodes[0].getrawmempool(True) - descendant_fees = 0 for x in reversed(chain): - descendant_fees += mempool[x]["fees"]["base"] if x == chain[-1]: assert_equal( mempool[x]["fees"]["modified"], mempool[x]["fees"]["base"] + satoshi_round(20.00), ) - assert_equal( - mempool[x]["fees"]["descendant"], descendant_fees + satoshi_round(20.00) - ) - # Check that node1's mempool is as expected (-> custom ancestor limit) + # Check that node1's mempool is as expected mempool0 = self.nodes[0].getrawmempool(False) mempool1 = self.nodes[1].getrawmempool(False) - assert_equal(len(mempool1), MAX_ANCESTORS_CUSTOM) - assert set(mempool1).issubset(set(mempool0)) - for tx in chain[:MAX_ANCESTORS_CUSTOM]: - assert tx in mempool1 + assert_equal(mempool0, mempool1) # TODO: more detailed check of node1's mempool (fees etc.) # check transaction unbroadcast info (should be false if in both # mempools) @@ -265,129 +165,6 @@ for tx in mempool: assert_equal(mempool[tx]["unbroadcast"], False) - # TODO: test ancestor size limits - - # Now test descendant chain limits - txid = utxo[1]["txid"] - value = utxo[1]["amount"] - vout = utxo[1]["vout"] - - transaction_package = [] - tx_children = [] - # First create one parent tx with 10 children - (txid, sent_value) = self.chain_transaction( - self.nodes[0], txid, vout, value, fee, 10 - ) - parent_transaction = txid - for i in range(10): - transaction_package.append({"txid": txid, "vout": i, "amount": sent_value}) - - # Sign and send up to MAX_DESCENDANT transactions chained off the - # parent tx - for _ in range(MAX_DESCENDANTS - 1): - utxo = transaction_package.pop(0) - (txid, sent_value) = self.chain_transaction( - self.nodes[0], utxo["txid"], utxo["vout"], utxo["amount"], fee, 10 - ) - if utxo["txid"] is parent_transaction: - tx_children.append(txid) - for j in range(10): - transaction_package.append( - {"txid": txid, "vout": j, "amount": sent_value} - ) - - mempool = self.nodes[0].getrawmempool(True) - assert_equal(mempool[parent_transaction]["descendantcount"], MAX_DESCENDANTS) - assert_equal( - sorted(mempool[parent_transaction]["spentby"]), sorted(tx_children) - ) - - for child in tx_children: - assert_equal(mempool[child]["depends"], [parent_transaction]) - - # Sending one more chained transaction will fail - utxo = transaction_package.pop(0) - assert_raises_rpc_error( - -26, - "too-long-mempool-chain", - self.chain_transaction, - self.nodes[0], - utxo["txid"], - utxo["vout"], - utxo["amount"], - fee, - 10, - ) - - # TODO: check that node1's mempool is as expected - - # TODO: test descendant size limits - - # Test reorg handling - # First, the basics: - self.generate(self.nodes[0], 1) - self.nodes[1].invalidateblock(self.nodes[0].getbestblockhash()) - self.nodes[1].reconsiderblock(self.nodes[0].getbestblockhash()) - - # Now test the case where node1 has a transaction T in its mempool that - # depends on transactions A and B which are in a mined block, and the - # block containing A and B is disconnected, AND B is not accepted back - # into node1's mempool because its ancestor count is too high. - - # Create 8 transactions, like so: - # Tx0 -> Tx1 (vout0) - # \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7 - # - # Mine them in the next block, then generate a new tx8 that spends - # Tx1 and Tx7, and add to node1's mempool, then disconnect the - # last block. - - # Create tx0 with 2 outputs - utxo = self.nodes[0].listunspent() - txid = utxo[0]["txid"] - value = utxo[0]["amount"] - vout = utxo[0]["vout"] - - send_value = satoshi_round((value - fee) / 2) - inputs = [{"txid": txid, "vout": vout}] - outputs = {} - for _ in range(2): - outputs[self.nodes[0].getnewaddress()] = send_value - rawtx = self.nodes[0].createrawtransaction(inputs, outputs) - signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) - txid = self.nodes[0].sendrawtransaction(signedtx["hex"]) - tx0_id = txid - value = send_value - - # Create tx1 - tx1_id, _ = self.chain_transaction(self.nodes[0], tx0_id, 0, value, fee, 1) - - # Create tx2-7 - vout = 1 - txid = tx0_id - for _ in range(6): - (txid, sent_value) = self.chain_transaction( - self.nodes[0], txid, vout, value, fee, 1 - ) - vout = 0 - value = sent_value - - # Mine these in a block - self.generate(self.nodes[0], 1) - - # Now generate tx8, with a big fee - inputs = [{"txid": tx1_id, "vout": 0}, {"txid": txid, "vout": 0}] - outputs = {self.nodes[0].getnewaddress(): send_value + value - 4 * fee} - rawtx = self.nodes[0].createrawtransaction(inputs, outputs) - signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) - txid = self.nodes[0].sendrawtransaction(signedtx["hex"]) - self.sync_mempools() - - # Now try to disconnect the tip on each node... - self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash()) - self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) - self.sync_blocks() - if __name__ == "__main__": MempoolPackagesTest().main() diff --git a/test/functional/mempool_updatefromblock.py b/test/functional/mempool_updatefromblock.py --- a/test/functional/mempool_updatefromblock.py +++ b/test/functional/mempool_updatefromblock.py @@ -17,16 +17,7 @@ class MempoolUpdateFromBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.limit_ancestor_descendant_count = 60 - self.extra_args = [ - [ - "-limitdescendantsize=5000", - "-limitancestorsize=5000", - f"-limitancestorcount={self.limit_ancestor_descendant_count}", - f"-limitdescendantcount={self.limit_ancestor_descendant_count}", - "-deprecatedrpc=mempool_ancestors_descendants", - ], - ] + self.ancestor_descendant_count = 60 def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -143,28 +134,16 @@ f"the mempool in {end - start} seconds." ) - self.log.info( - "Checking descendants/ancestors properties of all of the" - " in-mempool transactions..." - ) - for k, tx in enumerate(tx_id): - self.log.debug(f"Check transaction #{k}.") - entry = self.nodes[0].getmempoolentry(tx) - assert_equal(entry["descendantcount"], size - k) - assert_equal(entry["descendantsize"], sum(tx_size[k:size])) - assert_equal(entry["ancestorcount"], k + 1) - assert_equal(entry["ancestorsize"], sum(tx_size[0 : (k + 1)])) - def run_test(self): # Mine the transactions in batches so we get reorg_depth blocks # reorg'ed reorg_depth = 4 self.transaction_graph_test( - size=self.limit_ancestor_descendant_count, + size=self.ancestor_descendant_count, n_tx_to_mine=range( 0, - self.limit_ancestor_descendant_count, - self.limit_ancestor_descendant_count // reorg_depth, + self.ancestor_descendant_count, + self.ancestor_descendant_count // reorg_depth, ), ) diff --git a/test/functional/rpc_deprecated.py b/test/functional/rpc_deprecated.py --- a/test/functional/rpc_deprecated.py +++ b/test/functional/rpc_deprecated.py @@ -42,12 +42,6 @@ "-deprecatedrpc=isfinaltransaction_noerror", "-deprecatedrpc=getblocktemplate_sigops", "-deprecatedrpc=softforks", - # This test checks for the presence of the ancestor count in - # the listunspent output. However this is only displayed if the - # count is non-null, which will no longer be the case after - # wellington activation. - f"-wellingtonactivationtime={FAR_IN_THE_FUTURE}", - "-deprecatedrpc=mempool_ancestors_descendants", ], ] @@ -161,24 +155,6 @@ res = self.nodes[1].getblockchaininfo() assert_equal(res["softforks"], {}) - txid = self.nodes[0].sendtoaddress( - self.nodes[0].get_deterministic_priv_key().address, 1000000 - ) - assert txid in self.nodes[0].getrawmempool() - utxo = self.nodes[0].listunspent(minconf=0, maxconf=0)[0] - assert "ancestorcount" not in utxo - assert "ancestorsize" not in utxo - assert "ancestorfees" not in utxo - - txid = self.nodes[1].sendtoaddress( - self.nodes[1].get_deterministic_priv_key().address, 1000000 - ) - assert txid in self.nodes[1].getrawmempool() - utxo = self.nodes[1].listunspent(minconf=0, maxconf=0)[0] - assert "ancestorcount" in utxo - assert "ancestorsize" in utxo - assert "ancestorfees" in utxo - if __name__ == "__main__": DeprecatedRpcTest().main() 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,12 +16,8 @@ ) 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 @@ -29,10 +25,6 @@ [ "-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 @@ -609,82 +601,6 @@ assert_equal(coinbase_tx_1["transactions"][0]["blockhash"], blocks[1]) assert_equal(len(self.nodes[0].listsinceblock(blocks[1])["transactions"]), 0) - # ==Check that wallet prefers to use coins that don't exceed mempool li - - # Get all non-zero utxos together - chain_addrs = [self.nodes[0].getnewaddress(), self.nodes[0].getnewaddress()] - singletxid = self.nodes[0].sendtoaddress( - chain_addrs[0], self.nodes[0].getbalance(), "", "", True - ) - self.generate(self.nodes[0], 1, sync_fun=self.no_op) - node0_balance = self.nodes[0].getbalance() - # Split into two chains - rawtx = self.nodes[0].createrawtransaction( - [{"txid": singletxid, "vout": 0}], - { - chain_addrs[0]: node0_balance / 2 - Decimal("10000"), - chain_addrs[1]: node0_balance / 2 - Decimal("10000"), - }, - ) - signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) - singletxid = self.nodes[0].sendrawtransaction( - hexstring=signedtx["hex"], maxfeerate=0 - ) - self.generate(self.nodes[0], 1, sync_fun=self.no_op) - - # Make a long chain of unconfirmed payments without hitting mempool limit - # Each tx we make leaves only one output of change on a chain 1 longer - # Since the amount to send is always much less than the outputs, we only ever need one output - # So we should be able to generate exactly chainlimit txs for each - # original output - sending_addr = self.nodes[1].getnewaddress() - txid_list = [] - for _ in range(chainlimit * 2): - txid_list.append( - self.nodes[0].sendtoaddress(sending_addr, Decimal("10000")) - ) - assert_equal(self.nodes[0].getmempoolinfo()["size"], chainlimit * 2) - assert_equal(len(txid_list), chainlimit * 2) - - # Without walletrejectlongchains, we will still generate a txid - # The tx will be stored in the wallet but not accepted to the mempool - extra_txid = self.nodes[0].sendtoaddress(sending_addr, Decimal("10000")) - assert extra_txid not in self.nodes[0].getrawmempool() - assert extra_txid in [tx["txid"] for tx in self.nodes[0].listtransactions()] - self.nodes[0].abandontransaction(extra_txid) - total_txs = len(self.nodes[0].listtransactions("*", 99999)) - - # Try with walletrejectlongchains - # Double chain limit but require combining inputs, so we pass - # SelectCoinsMinConf - self.stop_node(0) - self.start_node( - 0, - self.extra_args[0] - + ["-walletrejectlongchains", f"-limitancestorcount={str(2 * chainlimit)}"], - ) - - # wait until the wallet has submitted all transactions to the mempool - self.wait_until(lambda: len(self.nodes[0].getrawmempool()) == chainlimit * 2) - - # Prevent potential race condition when calling wallet RPCs right after - # restart - self.nodes[0].syncwithvalidationinterfacequeue() - - node0_balance = self.nodes[0].getbalance() - # With walletrejectlongchains we will not create the tx and store it in - # our wallet. - assert_raises_rpc_error( - -6, - "Transaction has too long of a mempool chain", - self.nodes[0].sendtoaddress, - sending_addr, - node0_balance - Decimal("10000"), - ) - - # Verify nothing new in wallet - assert_equal(total_txs, len(self.nodes[0].listtransactions("*", 99999))) - # Test getaddressinfo on external address. Note that these addresses # are taken from disablewallet.py assert_raises_rpc_error(