miner: empty mempool special case for waitNext()

Block template fees are calculated by looping over new_tmpl->vTxFees
and return (early) once the fee_threshold is exceeded.

This left an edge case when the mempool is empty, which this commit
fixes and adds a test for.

Also update interface_ipc.py to account for the new behavior.
This commit is contained in:
Sjors Provoost 2025-10-07 17:28:29 +02:00
parent 919e6d01e9
commit 2e8fff3f17
No known key found for this signature in database
GPG Key ID: 57FF9BDBCC301009
3 changed files with 24 additions and 3 deletions

View File

@ -528,6 +528,9 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma
}
}
// Special case for when fee threshold is zero and the mempool is and was empty
if (current_fees == 0 && options.fee_threshold == 0 && new_tmpl->vTxFees.empty()) return new_tmpl;
CAmount new_fees = 0;
for (CAmount fee : new_tmpl->vTxFees) {
new_fees += fee;

View File

@ -113,6 +113,22 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
options.coinbase_output_script = scriptPubKey;
LOCK(tx_mempool.cs);
BOOST_CHECK(tx_mempool.size() == 0);
// Block template should only have a coinbase when there's nothing in the mempool
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options);
BOOST_REQUIRE(block_template);
CBlock block{block_template->getBlock()};
BOOST_REQUIRE_EQUAL(block.vtx.size(), 1U);
// waitNext() on an empty mempool should return nullptr because there is no better template
auto should_be_nullptr = block_template->waitNext({.timeout = MillisecondsDouble{0}, .fee_threshold = 1});
BOOST_REQUIRE(should_be_nullptr == nullptr);
// Unless fee_threshold is 0
block_template = block_template->waitNext({.timeout = MillisecondsDouble{0}, .fee_threshold = 0});
BOOST_REQUIRE(block_template);
// Test the ancestor feerate transaction selection.
TestMemPoolEntryHelper entry;
@ -144,9 +160,9 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
const auto high_fee_tx{entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)};
AddToMempool(tx_mempool, high_fee_tx);
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options);
block_template = mining->createNewBlock(options);
BOOST_REQUIRE(block_template);
CBlock block{block_template->getBlock()};
block = block_template->getBlock();
BOOST_REQUIRE_EQUAL(block.vtx.size(), 4U);
BOOST_CHECK(block.vtx[1]->GetHash() == hashParentTx);
BOOST_CHECK(block.vtx[2]->GetHash() == hashHighFeeTx);
@ -184,7 +200,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
AddToMempool(tx_mempool, entry.Fee(feeToUse).FromTx(tx));
// waitNext() should return nullptr because there is no better template
auto should_be_nullptr = block_template->waitNext({.timeout = MillisecondsDouble{0}, .fee_threshold = 1});
should_be_nullptr = block_template->waitNext({.timeout = MillisecondsDouble{0}, .fee_threshold = 1});
BOOST_REQUIRE(should_be_nullptr == nullptr);
block = block_template->getBlock();

View File

@ -153,6 +153,7 @@ class IPCInterfaceTest(BitcoinTestFramework):
self.log.debug("Wait for a new template")
waitoptions = self.capnp_modules['mining'].BlockWaitOptions()
waitoptions.timeout = timeout
waitoptions.feeThreshold = 1
waitnext = template.result.waitNext(ctx, waitoptions)
self.generate(self.nodes[0], 1)
template2 = await waitnext
@ -168,6 +169,7 @@ class IPCInterfaceTest(BitcoinTestFramework):
block3 = await self.parse_and_deserialize_block(template4, ctx)
assert_equal(len(block3.vtx), 2)
self.log.debug("Wait again, this should return the same template, since the fee threshold is zero")
waitoptions.feeThreshold = 0
template5 = await template4.result.waitNext(ctx, waitoptions)
block4 = await self.parse_and_deserialize_block(template5, ctx)
assert_equal(len(block4.vtx), 2)