Skip to content

Commit b2e7811

Browse files
author
MacroFake
committed
Merge bitcoin/bitcoin#24538: miner: bug fix? update for ancestor inclusion using modified fees, not base
e4303c3 [unit test] prioritisation in mining (glozow) 7a8d606 [miner] bug fix: update for parent inclusion using modified fee (glozow) 0f9a444 MOVEONLY: group miner tests into MinerTestingSetup functions (glozow) Pull request description: Came up while reviewing #24364, where some of us incorrectly assumed that we use the same fee deduction in `CTxMemPoolModifiedEntry::nModFeesWithAncestors` when first constructing an entry and in `update_for_parent_inclusion`. Actually, the behavior is this: when a mempool entry's ancestor is included in the block template, we create a `CTxMemPoolModifiedEntry` for it, subtracting the ancestor's modified fees from `nModFeesWithAncestors`. If another ancestor is included, we update it again, but use the ancestor's _base_ fees instead. I can't explain why we use `GetFee` in one place and `GetModifiedFee` in the other, but I'm quite certain we should be using the same one for both. And should it be base or modified fees? Modified, otherwise the child inherits the prioritisation of the parent, but only until the parent gets mined. If we want prioritisation to cascade down to current in-mempool descendants, we should probably document that in the `prioritsetransaction` helpstring and implement it in `CTxMemPool::mapDeltas`, not as a quirk in the mining code? Wrote a test in which a mempool entry has 2 ancestors, both prioritised, and both included in a block template individually. This test should fail without the s/GetFee/GetModifiedFee commit. ACKs for top commit: ccdle12: tested ACK e4303c3 MarcoFalke: ACK e4303c3 🚗 Tree-SHA512: 4cd94106fbc9353e9f9b6d5af268ecda5aec7539245298c940ca220606dd0737264505bfaae1f83d94765cc2d9e1a6e913a765048fe6c19292482241761a6762
2 parents 74d9f4b + e4303c3 commit b2e7811

File tree

2 files changed

+138
-49
lines changed

2 files changed

+138
-49
lines changed

src/node/miner.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ struct update_for_parent_inclusion
116116

117117
void operator() (CTxMemPoolModifiedEntry &e)
118118
{
119-
e.nModFeesWithAncestors -= iter->GetFee();
119+
e.nModFeesWithAncestors -= iter->GetModifiedFee();
120120
e.nSizeWithAncestors -= iter->GetTxSize();
121121
e.nSigOpCostWithAncestors -= iter->GetSigOpCost();
122122
}

src/test/miner_tests.cpp

Lines changed: 137 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ using node::CBlockTemplate;
3030
namespace miner_tests {
3131
struct MinerTestingSetup : public TestingSetup {
3232
void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
33+
void TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
34+
void TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
3335
bool TestSequenceLocks(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs)
3436
{
3537
CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool);
@@ -191,60 +193,17 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
191193
BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2);
192194
}
193195

194-
// NOTE: These tests rely on CreateNewBlock doing its own self-validation!
195-
BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
196+
void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight)
196197
{
197-
// Note that by default, these tests run with size accounting enabled.
198-
const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
199-
const CChainParams& chainparams = *chainParams;
200-
CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
201-
std::unique_ptr<CBlockTemplate> pblocktemplate;
202-
CMutableTransaction tx;
203-
CScript script;
204198
uint256 hash;
199+
CMutableTransaction tx;
205200
TestMemPoolEntryHelper entry;
206201
entry.nFee = 11;
207202
entry.nHeight = 11;
208203

209-
fCheckpointsEnabled = false;
210-
211-
// Simple block creation, nothing special yet:
212-
BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
213-
214-
// We can't make transactions until we have inputs
215-
// Therefore, load 110 blocks :)
216-
static_assert(std::size(BLOCKINFO) == 110, "Should have 110 blocks to import");
217-
int baseheight = 0;
218-
std::vector<CTransactionRef> txFirst;
219-
for (const auto& bi : BLOCKINFO) {
220-
CBlock *pblock = &pblocktemplate->block; // pointer for convenience
221-
{
222-
LOCK(cs_main);
223-
pblock->nVersion = VERSIONBITS_TOP_BITS;
224-
pblock->nTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1;
225-
CMutableTransaction txCoinbase(*pblock->vtx[0]);
226-
txCoinbase.nVersion = 1;
227-
txCoinbase.vin[0].scriptSig = CScript{} << (m_node.chainman->ActiveChain().Height() + 1) << bi.extranonce;
228-
txCoinbase.vout.resize(1); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this)
229-
txCoinbase.vout[0].scriptPubKey = CScript();
230-
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
231-
if (txFirst.size() == 0)
232-
baseheight = m_node.chainman->ActiveChain().Height();
233-
if (txFirst.size() < 4)
234-
txFirst.push_back(pblock->vtx[0]);
235-
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
236-
pblock->nNonce = bi.nonce;
237-
}
238-
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
239-
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr));
240-
pblock->hashPrevBlock = pblock->GetHash();
241-
}
242-
243-
LOCK(cs_main);
244-
LOCK(m_node.mempool->cs);
245-
246204
// Just to make sure we can still make simple blocks
247-
BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
205+
auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
206+
BOOST_CHECK(pblocktemplate);
248207

249208
const CAmount BLOCKSUBSIDY = 50*COIN;
250209
const CAmount LOWFEE = CENT;
@@ -386,7 +345,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
386345
tx.vin[0].prevout.n = 0;
387346
tx.vin[0].scriptSig = CScript() << OP_1;
388347
tx.vout[0].nValue = BLOCKSUBSIDY-LOWFEE;
389-
script = CScript() << OP_0;
348+
CScript script = CScript() << OP_0;
390349
tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script));
391350
hash = tx.GetHash();
392351
m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
@@ -508,13 +467,143 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
508467

509468
BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
510469
BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 5U);
470+
}
471+
472+
void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst)
473+
{
474+
TestMemPoolEntryHelper entry;
475+
476+
// Test that a tx below min fee but prioritised is included
477+
CMutableTransaction tx;
478+
tx.vin.resize(1);
479+
tx.vin[0].prevout.hash = txFirst[0]->GetHash();
480+
tx.vin[0].prevout.n = 0;
481+
tx.vin[0].scriptSig = CScript() << OP_1;
482+
tx.vout.resize(1);
483+
tx.vout[0].nValue = 5000000000LL; // 0 fee
484+
uint256 hashFreePrioritisedTx = tx.GetHash();
485+
m_node.mempool->addUnchecked(entry.Fee(0).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
486+
m_node.mempool->PrioritiseTransaction(hashFreePrioritisedTx, 5 * COIN);
487+
488+
tx.vin[0].prevout.hash = txFirst[1]->GetHash();
489+
tx.vin[0].prevout.n = 0;
490+
tx.vout[0].nValue = 5000000000LL - 1000;
491+
// This tx has a low fee: 1000 satoshis
492+
uint256 hashParentTx = tx.GetHash(); // save this txid for later use
493+
m_node.mempool->addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
494+
495+
// This tx has a medium fee: 10000 satoshis
496+
tx.vin[0].prevout.hash = txFirst[2]->GetHash();
497+
tx.vout[0].nValue = 5000000000LL - 10000;
498+
uint256 hashMediumFeeTx = tx.GetHash();
499+
m_node.mempool->addUnchecked(entry.Fee(10000).Time(GetTime()).SpendsCoinbase(true).FromTx(tx));
500+
m_node.mempool->PrioritiseTransaction(hashMediumFeeTx, -5 * COIN);
501+
502+
// This tx also has a low fee, but is prioritised
503+
tx.vin[0].prevout.hash = hashParentTx;
504+
tx.vout[0].nValue = 5000000000LL - 1000 - 1000; // 1000 satoshi fee
505+
uint256 hashPrioritsedChild = tx.GetHash();
506+
m_node.mempool->addUnchecked(entry.Fee(1000).Time(GetTime()).SpendsCoinbase(false).FromTx(tx));
507+
m_node.mempool->PrioritiseTransaction(hashPrioritsedChild, 2 * COIN);
508+
509+
// Test that transaction selection properly updates ancestor fee calculations as prioritised
510+
// parents get included in a block. Create a transaction with two prioritised ancestors, each
511+
// included by itself: FreeParent <- FreeChild <- FreeGrandchild.
512+
// When FreeParent is added, a modified entry will be created for FreeChild + FreeGrandchild
513+
// FreeParent's prioritisation should not be included in that entry.
514+
// When FreeChild is included, FreeChild's prioritisation should also not be included.
515+
tx.vin[0].prevout.hash = txFirst[3]->GetHash();
516+
tx.vout[0].nValue = 5000000000LL; // 0 fee
517+
uint256 hashFreeParent = tx.GetHash();
518+
m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
519+
m_node.mempool->PrioritiseTransaction(hashFreeParent, 10 * COIN);
520+
521+
tx.vin[0].prevout.hash = hashFreeParent;
522+
tx.vout[0].nValue = 5000000000LL; // 0 fee
523+
uint256 hashFreeChild = tx.GetHash();
524+
m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
525+
m_node.mempool->PrioritiseTransaction(hashFreeChild, 1 * COIN);
526+
527+
tx.vin[0].prevout.hash = hashFreeChild;
528+
tx.vout[0].nValue = 5000000000LL; // 0 fee
529+
uint256 hashFreeGrandchild = tx.GetHash();
530+
m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
531+
532+
auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
533+
BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U);
534+
BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashFreeParent);
535+
BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashFreePrioritisedTx);
536+
BOOST_CHECK(pblocktemplate->block.vtx[3]->GetHash() == hashParentTx);
537+
BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashPrioritsedChild);
538+
BOOST_CHECK(pblocktemplate->block.vtx[5]->GetHash() == hashFreeChild);
539+
for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) {
540+
// The FreeParent and FreeChild's prioritisations should not impact the child.
541+
BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashFreeGrandchild);
542+
// De-prioritised transaction should not be included.
543+
BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashMediumFeeTx);
544+
}
545+
}
546+
547+
// NOTE: These tests rely on CreateNewBlock doing its own self-validation!
548+
BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
549+
{
550+
// Note that by default, these tests run with size accounting enabled.
551+
const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
552+
const CChainParams& chainparams = *chainParams;
553+
CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
554+
std::unique_ptr<CBlockTemplate> pblocktemplate;
555+
556+
fCheckpointsEnabled = false;
557+
558+
// Simple block creation, nothing special yet:
559+
BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
560+
561+
// We can't make transactions until we have inputs
562+
// Therefore, load 110 blocks :)
563+
static_assert(std::size(BLOCKINFO) == 110, "Should have 110 blocks to import");
564+
int baseheight = 0;
565+
std::vector<CTransactionRef> txFirst;
566+
for (const auto& bi : BLOCKINFO) {
567+
CBlock *pblock = &pblocktemplate->block; // pointer for convenience
568+
{
569+
LOCK(cs_main);
570+
pblock->nVersion = VERSIONBITS_TOP_BITS;
571+
pblock->nTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1;
572+
CMutableTransaction txCoinbase(*pblock->vtx[0]);
573+
txCoinbase.nVersion = 1;
574+
txCoinbase.vin[0].scriptSig = CScript{} << (m_node.chainman->ActiveChain().Height() + 1) << bi.extranonce;
575+
txCoinbase.vout.resize(1); // Ignore the (optional) segwit commitment added by CreateNewBlock (as the hardcoded nonces don't account for this)
576+
txCoinbase.vout[0].scriptPubKey = CScript();
577+
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
578+
if (txFirst.size() == 0)
579+
baseheight = m_node.chainman->ActiveChain().Height();
580+
if (txFirst.size() < 4)
581+
txFirst.push_back(pblock->vtx[0]);
582+
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
583+
pblock->nNonce = bi.nonce;
584+
}
585+
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
586+
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr));
587+
pblock->hashPrevBlock = pblock->GetHash();
588+
}
589+
590+
LOCK(cs_main);
591+
LOCK(m_node.mempool->cs);
592+
593+
TestBasicMining(chainparams, scriptPubKey, txFirst, baseheight);
511594

512595
m_node.chainman->ActiveChain().Tip()->nHeight--;
513596
SetMockTime(0);
514597
m_node.mempool->clear();
515598

516599
TestPackageSelection(chainparams, scriptPubKey, txFirst);
517600

601+
m_node.chainman->ActiveChain().Tip()->nHeight--;
602+
SetMockTime(0);
603+
m_node.mempool->clear();
604+
605+
TestPrioritisedMining(chainparams, scriptPubKey, txFirst);
606+
518607
fCheckpointsEnabled = true;
519608
}
520609

0 commit comments

Comments
 (0)