Skip to content

Commit b282987

Browse files
MacroFakeknst
authored andcommitted
Merge 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 bitcoin#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
1 parent a061cb9 commit b282987

File tree

2 files changed

+151
-61
lines changed

2 files changed

+151
-61
lines changed

src/node/miner.h

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

136136
void operator() (CTxMemPoolModifiedEntry &e)
137137
{
138-
e.nModFeesWithAncestors -= iter->GetFee();
138+
e.nModFeesWithAncestors -= iter->GetModifiedFee();
139139
e.nSizeWithAncestors -= iter->GetTxSize();
140140
e.nSigOpCountWithAncestors -= iter->GetSigOpCount();
141141
}

src/test/miner_tests.cpp

Lines changed: 150 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ using node::CBlockTemplate;
3737
namespace miner_tests {
3838
struct MinerTestingSetup : public TestingSetup {
3939
void TestPackageSelection(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
40+
void TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
41+
void TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs);
4042
bool TestSequenceLocks(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_node.mempool->cs)
4143
{
4244
CCoinsViewMemPool view_mempool(&m_node.chainman->ActiveChainstate().CoinsTip(), *m_node.mempool);
@@ -205,70 +207,16 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co
205207
BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2);
206208
}
207209

208-
// NOTE: These tests rely on CreateNewBlock doing its own self-validation!
209-
BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
210+
void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst, int baseheight)
210211
{
211-
const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
212-
const CChainParams& chainparams = *chainParams;
213-
CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
214-
std::unique_ptr<CBlockTemplate> pblocktemplate, pemptyblocktemplate;
215-
CMutableTransaction tx;
216-
CScript script;
217212
uint256 hash;
213+
CMutableTransaction tx;
218214
TestMemPoolEntryHelper entry;
219215
entry.nFee = 11;
220216
entry.nHeight = 11;
221-
222-
fCheckpointsEnabled = false;
223-
224-
// Simple block creation, nothing special yet:
225-
BOOST_CHECK(pemptyblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
226-
227-
// We can't make transactions until we have inputs
228-
// Therefore, load 100 blocks :)
229-
int baseheight = 0;
230-
std::vector<CTransactionRef> txFirst;
231-
232-
auto createAndProcessEmptyBlock = [&]() {
233-
int i = m_node.chainman->ActiveChain().Height() % blockinfo_size;
234-
CBlock *pblock = &pemptyblocktemplate->block; // pointer for convenience
235-
{
236-
LOCK(cs_main);
237-
pblock->nVersion = VERSIONBITS_TOP_BITS;
238-
pblock->nTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1;
239-
CMutableTransaction txCoinbase(*pblock->vtx[0]);
240-
txCoinbase.nVersion = 1;
241-
txCoinbase.vin[0].scriptSig = CScript{} << (m_node.chainman->ActiveChain().Height() + 1) << BLOCKINFO[i].extranonce;
242-
txCoinbase.vout[0].scriptPubKey = CScript();
243-
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
244-
if (txFirst.size() == 0)
245-
baseheight = m_node.chainman->ActiveChain().Height();
246-
if (txFirst.size() < 4)
247-
txFirst.push_back(pblock->vtx[0]);
248-
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
249-
pblock->nNonce = BLOCKINFO[i].nonce;
250-
251-
// This will usually succeed in the first round as we take the nonce from BLOCKINFO
252-
// It's however useful when adding new blocks with unknown nonces (you should add the found block to BLOCKINFO)
253-
while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, chainparams.GetConsensus())) {
254-
pblock->nNonce++;
255-
}
256-
}
257-
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
258-
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr));
259-
pblock->hashPrevBlock = pblock->GetHash();
260-
};
261-
262-
for ([[maybe_unused]] const auto& _ : BLOCKINFO) {
263-
createAndProcessEmptyBlock();
264-
}
265-
266-
{
267-
LOCK(cs_main);
268-
LOCK(m_node.mempool->cs);
269-
270217
// Just to make sure we can still make simple blocks
271-
BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
218+
auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
219+
BOOST_CHECK(pblocktemplate);
272220

273221
const CAmount BLOCKSUBSIDY = 500*COIN;
274222
const CAmount LOWFEE = CENT;
@@ -410,7 +358,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
410358
tx.vin[0].prevout.n = 0;
411359
tx.vin[0].scriptSig = CScript() << OP_1;
412360
tx.vout[0].nValue = BLOCKSUBSIDY-LOWFEE;
413-
script = CScript() << OP_0;
361+
CScript script = CScript() << OP_0;
414362
tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script));
415363
hash = tx.GetHash();
416364
m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
@@ -530,7 +478,143 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
530478
for (int i = 0; i < CBlockIndex::nMedianTimeSpan; ++i) {
531479
CBlockIndex* ancestor{Assert(m_node.chainman->ActiveChain().Tip()->GetAncestor(m_node.chainman->ActiveChain().Tip()->nHeight - i))};
532480
ancestor->nTime += SEQUENCE_LOCK_TIME; // Trick the MedianTimePast
533-
} // unlock cs_main while calling createAndProcessEmptyBlock
481+
}
482+
}
483+
484+
void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, const CScript& scriptPubKey, const std::vector<CTransactionRef>& txFirst)
485+
{
486+
TestMemPoolEntryHelper entry;
487+
488+
// Test that a tx below min fee but prioritised is included
489+
CMutableTransaction tx;
490+
tx.vin.resize(1);
491+
tx.vin[0].prevout.hash = txFirst[0]->GetHash();
492+
tx.vin[0].prevout.n = 0;
493+
tx.vin[0].scriptSig = CScript() << OP_1;
494+
tx.vout.resize(1);
495+
tx.vout[0].nValue = 5000000000LL; // 0 fee
496+
uint256 hashFreePrioritisedTx = tx.GetHash();
497+
m_node.mempool->addUnchecked(entry.Fee(0).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
498+
m_node.mempool->PrioritiseTransaction(hashFreePrioritisedTx, 5 * COIN);
499+
500+
tx.vin[0].prevout.hash = txFirst[1]->GetHash();
501+
tx.vin[0].prevout.n = 0;
502+
tx.vout[0].nValue = 5000000000LL - 1000;
503+
// This tx has a low fee: 1000 satoshis
504+
uint256 hashParentTx = tx.GetHash(); // save this txid for later use
505+
m_node.mempool->addUnchecked(entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
506+
507+
// This tx has a medium fee: 10000 satoshis
508+
tx.vin[0].prevout.hash = txFirst[2]->GetHash();
509+
tx.vout[0].nValue = 5000000000LL - 10000;
510+
uint256 hashMediumFeeTx = tx.GetHash();
511+
m_node.mempool->addUnchecked(entry.Fee(10000).Time(Now<NodeSeconds>()).SpendsCoinbase(true).FromTx(tx));
512+
m_node.mempool->PrioritiseTransaction(hashMediumFeeTx, -5 * COIN);
513+
514+
// This tx also has a low fee, but is prioritised
515+
tx.vin[0].prevout.hash = hashParentTx;
516+
tx.vout[0].nValue = 5000000000LL - 1000 - 1000; // 1000 satoshi fee
517+
uint256 hashPrioritsedChild = tx.GetHash();
518+
m_node.mempool->addUnchecked(entry.Fee(1000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
519+
m_node.mempool->PrioritiseTransaction(hashPrioritsedChild, 2 * COIN);
520+
521+
// Test that transaction selection properly updates ancestor fee calculations as prioritised
522+
// parents get included in a block. Create a transaction with two prioritised ancestors, each
523+
// included by itself: FreeParent <- FreeChild <- FreeGrandchild.
524+
// When FreeParent is added, a modified entry will be created for FreeChild + FreeGrandchild
525+
// FreeParent's prioritisation should not be included in that entry.
526+
// When FreeChild is included, FreeChild's prioritisation should also not be included.
527+
tx.vin[0].prevout.hash = txFirst[3]->GetHash();
528+
tx.vout[0].nValue = 5000000000LL; // 0 fee
529+
uint256 hashFreeParent = tx.GetHash();
530+
m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
531+
m_node.mempool->PrioritiseTransaction(hashFreeParent, 10 * COIN);
532+
533+
tx.vin[0].prevout.hash = hashFreeParent;
534+
tx.vout[0].nValue = 5000000000LL; // 0 fee
535+
uint256 hashFreeChild = tx.GetHash();
536+
m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
537+
m_node.mempool->PrioritiseTransaction(hashFreeChild, 1 * COIN);
538+
539+
tx.vin[0].prevout.hash = hashFreeChild;
540+
tx.vout[0].nValue = 5000000000LL; // 0 fee
541+
uint256 hashFreeGrandchild = tx.GetHash();
542+
m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx));
543+
544+
auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey);
545+
BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U);
546+
BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashFreeParent);
547+
BOOST_CHECK(pblocktemplate->block.vtx[2]->GetHash() == hashFreePrioritisedTx);
548+
BOOST_CHECK(pblocktemplate->block.vtx[3]->GetHash() == hashParentTx);
549+
BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashPrioritsedChild);
550+
BOOST_CHECK(pblocktemplate->block.vtx[5]->GetHash() == hashFreeChild);
551+
for (size_t i=0; i<pblocktemplate->block.vtx.size(); ++i) {
552+
// The FreeParent and FreeChild's prioritisations should not impact the child.
553+
BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashFreeGrandchild);
554+
// De-prioritised transaction should not be included.
555+
BOOST_CHECK(pblocktemplate->block.vtx[i]->GetHash() != hashMediumFeeTx);
556+
}
557+
}
558+
559+
// NOTE: These tests rely on CreateNewBlock doing its own self-validation!
560+
BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
561+
{
562+
// Note that by default, these tests run with size accounting enabled.
563+
const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
564+
const CChainParams& chainparams = *chainParams;
565+
CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
566+
std::unique_ptr<CBlockTemplate> pblocktemplate, pemptyblocktemplate;
567+
568+
fCheckpointsEnabled = false;
569+
570+
// Simple block creation, nothing special yet:
571+
BOOST_CHECK(pemptyblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey));
572+
573+
// We can't make transactions until we have inputs
574+
// Therefore, load 100 blocks :)
575+
int baseheight = 0;
576+
std::vector<CTransactionRef> txFirst;
577+
578+
auto createAndProcessEmptyBlock = [&]() {
579+
int i = m_node.chainman->ActiveChain().Height() % blockinfo_size;
580+
CBlock *pblock = &pemptyblocktemplate->block; // pointer for convenience
581+
{
582+
LOCK(cs_main);
583+
pblock->nVersion = VERSIONBITS_TOP_BITS;
584+
pblock->nTime = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1;
585+
CMutableTransaction txCoinbase(*pblock->vtx[0]);
586+
txCoinbase.nVersion = 1;
587+
txCoinbase.vin[0].scriptSig = CScript{} << (m_node.chainman->ActiveChain().Height() + 1) << BLOCKINFO[i].extranonce;
588+
txCoinbase.vout[0].scriptPubKey = CScript();
589+
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
590+
if (txFirst.size() == 0)
591+
baseheight = m_node.chainman->ActiveChain().Height();
592+
if (txFirst.size() < 4)
593+
txFirst.push_back(pblock->vtx[0]);
594+
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
595+
pblock->nNonce = BLOCKINFO[i].nonce;
596+
597+
// This will usually succeed in the first round as we take the nonce from BLOCKINFO
598+
// It's however useful when adding new blocks with unknown nonces (you should add the found block to BLOCKINFO)
599+
while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, chainparams.GetConsensus())) {
600+
pblock->nNonce++;
601+
}
602+
}
603+
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
604+
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr));
605+
pblock->hashPrevBlock = pblock->GetHash();
606+
};
607+
608+
for ([[maybe_unused]] const auto& _ : BLOCKINFO) {
609+
createAndProcessEmptyBlock();
610+
}
611+
612+
613+
{
614+
LOCK(cs_main);
615+
LOCK(m_node.mempool->cs);
616+
617+
TestBasicMining(chainparams, scriptPubKey, txFirst, baseheight);
534618
}
535619

536620
// Mine an empty block
@@ -554,6 +638,12 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
554638
LOCK2(cs_main, m_node.mempool->cs);
555639
TestPackageSelection(chainparams, scriptPubKey, txFirst);
556640

641+
m_node.chainman->ActiveChain().Tip()->nHeight--;
642+
SetMockTime(0);
643+
m_node.mempool->clear();
644+
645+
TestPrioritisedMining(chainparams, scriptPubKey, txFirst);
646+
557647
fCheckpointsEnabled = true;
558648
}
559649

0 commit comments

Comments
 (0)