v31.1-roots.1 · Commit 4: [PATCH 04/17] feat(mining): preserve Roots priority and template policies
src/node/miner.cpp
Before: src/node/miner.cpp · After: src/node/miner.cpp
File metadata
index 7ea8d10b73064724ab4369aca08cc7e6e6342cd8..1d1fd69ac866973b5026488e77c30f12a29fec9c 100644
Download original patch · Release notes
Diff rows 1–248 of 248. Line numbers refer to the original before/after files.
+ Added− RemovedUnmarked lines are context.
Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.
src/node/miner.cpp
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
#include <validation.h>
|
|
29
29
|
|
|
30
30
|
#include <algorithm>
|
|
31
|
+
#include <map>
|
|
31
32
|
#include <utility>
|
|
32
33
|
#include <numeric>
|
|
33
34
|
|
|
@@ -81,6 +82,8 @@ static BlockAssembler::Options ClampOptions(BlockAssembler::Options options)
|
|
|
81
82
|
// Apply DEFAULT_BLOCK_RESERVED_WEIGHT when the caller left it unset.
|
|
82
83
|
options.block_reserved_weight = std::clamp<size_t>(options.block_reserved_weight.value_or(DEFAULT_BLOCK_RESERVED_WEIGHT), MINIMUM_BLOCK_RESERVED_WEIGHT, MAX_BLOCK_WEIGHT);
|
|
83
84
|
options.coinbase_output_max_additional_sigops = std::clamp<size_t>(options.coinbase_output_max_additional_sigops, 0, MAX_BLOCK_SIGOPS_COST);
|
|
85
|
+
options.nBlockMaxSize = std::clamp<size_t>(options.nBlockMaxSize, DEFAULT_BLOCK_RESERVED_SIZE, MAX_BLOCK_SERIALIZED_SIZE);
|
|
86
|
+
options.nBlockPrioritySize = std::min(options.nBlockPrioritySize, options.nBlockMaxSize);
|
|
84
87
|
// Limit weight to between block_reserved_weight and MAX_BLOCK_WEIGHT for sanity:
|
|
85
88
|
// block_reserved_weight can safely exceed -blockmaxweight, but the rest of the block template will be empty.
|
|
86
89
|
options.nBlockMaxWeight = std::clamp<size_t>(options.nBlockMaxWeight, *options.block_reserved_weight, MAX_BLOCK_WEIGHT);
|
|
@@ -98,7 +101,16 @@ BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool
|
|
|
98
101
|
void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& options)
|
|
99
102
|
{
|
|
100
103
|
// Block resource limits
|
|
101
|
-
options.nBlockMaxWeight = args.GetIntArg("-blockmaxweight", options.nBlockMaxWeight);
|
|
104
|
+
const bool weight_set{args.IsArgSet("-blockmaxweight")};
|
|
105
|
+
if (weight_set) {
|
|
106
|
+
options.nBlockMaxWeight = args.GetIntArg("-blockmaxweight", DEFAULT_BLOCK_MAX_WEIGHT);
|
|
107
|
+
options.nBlockMaxSize = MAX_BLOCK_SERIALIZED_SIZE;
|
|
108
|
+
}
|
|
109
|
+
if (args.IsArgSet("-blockmaxsize")) {
|
|
110
|
+
options.nBlockMaxSize = args.GetIntArg("-blockmaxsize", MAX_BLOCK_SERIALIZED_SIZE);
|
|
111
|
+
if (!weight_set) options.nBlockMaxWeight = MAX_BLOCK_WEIGHT;
|
|
112
|
+
}
|
|
113
|
+
options.nBlockPrioritySize = args.GetIntArg("-blockprioritysize", options.nBlockPrioritySize);
|
|
102
114
|
if (const auto blockmintxfee{args.GetArg("-blockmintxfee")}) {
|
|
103
115
|
if (const auto parsed{ParseMoney(*blockmintxfee)}) options.blockMinFeeRate = CFeeRate{*parsed};
|
|
104
116
|
}
|
|
@@ -111,6 +123,8 @@ void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& optio
|
|
|
111
123
|
void BlockAssembler::resetBlock()
|
|
112
124
|
{
|
|
113
125
|
// Reserve space for fixed-size block header, txs count, and coinbase tx.
|
|
126
|
+
m_in_block.clear();
|
|
127
|
+
nBlockSize = DEFAULT_BLOCK_RESERVED_SIZE;
|
|
114
128
|
nBlockWeight = *Assert(m_options.block_reserved_weight);
|
|
115
129
|
nBlockSigOpsCost = m_options.coinbase_output_max_additional_sigops;
|
|
116
130
|
|
|
@@ -149,9 +163,8 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock()
|
|
|
149
163
|
|
|
150
164
|
if (m_mempool) {
|
|
151
165
|
LOCK(m_mempool->cs);
|
|
152
|
-
m_mempool->StartBlockBuilding();
|
|
166
|
+
addPriorityTxs();
|
|
153
167
|
addChunks();
|
|
154
|
-
m_mempool->StopBlockBuilding();
|
|
155
168
|
}
|
|
156
169
|
|
|
157
170
|
const auto time_1{SteadyClock::now()};
|
|
@@ -236,8 +249,9 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock()
|
|
|
236
249
|
return std::move(pblocktemplate);
|
|
237
250
|
}
|
|
238
251
|
|
|
239
|
-
bool BlockAssembler::TestChunkBlockLimits(FeePerWeight chunk_feerate, int64_t chunk_sigops_cost) const
|
|
252
|
+
bool BlockAssembler::TestChunkBlockLimits(FeePerWeight chunk_feerate, int64_t chunk_sigops_cost, uint64_t chunk_size) const
|
|
240
253
|
{
|
|
254
|
+
if (nBlockSize + chunk_size >= m_options.nBlockMaxSize) return false;
|
|
241
255
|
if (nBlockWeight + chunk_feerate.size >= m_options.nBlockMaxWeight) {
|
|
242
256
|
return false;
|
|
243
257
|
}
|
|
@@ -261,6 +275,8 @@ bool BlockAssembler::TestChunkTransactions(const std::vector<CTxMemPoolEntryRef>
|
|
|
261
275
|
|
|
262
276
|
void BlockAssembler::AddToBlock(const CTxMemPoolEntry& entry)
|
|
263
277
|
{
|
|
278
|
+
Assert(m_in_block.insert(&entry).second);
|
|
279
|
+
nBlockSize += GetSerializeSize(TX_WITH_WITNESS(entry.GetTx()));
|
|
264
280
|
pblocktemplate->block.vtx.emplace_back(entry.GetSharedTx());
|
|
265
281
|
pblocktemplate->vTxFees.push_back(entry.GetFee());
|
|
266
282
|
pblocktemplate->vTxSigOpsCost.push_back(entry.GetSigOpCost());
|
|
@@ -276,60 +292,135 @@ void BlockAssembler::AddToBlock(const CTxMemPoolEntry& entry)
|
|
|
276
292
|
}
|
|
277
293
|
}
|
|
278
294
|
|
|
279
|
-
void BlockAssembler::addChunks()
|
|
295
|
+
namespace {
|
|
296
|
+
// Recompute from the active UTXO set, so confirmed former mempool parents age
|
|
297
|
+
// correctly after a tip change without maintaining an ancestor/priority cache.
|
|
298
|
+
double CoinAgePriority(const CTxMemPoolEntry& entry, const CCoinsViewCache& coins, int height)
|
|
280
299
|
{
|
|
281
|
-
// Limit the number of attempts to add transactions to the block when it is
|
|
282
|
-
// close to full; this is just a simple heuristic to finish quickly if the
|
|
283
|
-
// mempool has a lot of entries.
|
|
284
|
-
const int64_t MAX_CONSECUTIVE_FAILURES = 1000;
|
|
285
|
-
constexpr int32_t BLOCK_FULL_ENOUGH_WEIGHT_DELTA = 4000;
|
|
286
|
-
int64_t nConsecutiveFailed = 0;
|
|
287
|
-
|
|
288
|
-
std::vector<CTxMemPoolEntry::CTxMemPoolEntryRef> selected_transactions;
|
|
289
|
-
selected_transactions.reserve(MAX_CLUSTER_COUNT_LIMIT);
|
|
290
|
-
FeePerWeight chunk_feerate;
|
|
300
|
+
unsigned int modified_size = entry.GetTxSize();
|
|
301
|
+
double coin_age{0};
|
|
302
|
+
for (const auto& input : entry.GetTx().vin) {
|
|
303
|
+
const unsigned int offset = 41U + std::min<unsigned int>(110, input.scriptSig.size());
|
|
304
|
+
if (modified_size > offset) modified_size -= offset;
|
|
305
|
+
const Coin& coin = coins.AccessCoin(input.prevout);
|
|
306
|
+
if (!coin.IsSpent() && coin.nHeight <= height) {
|
|
307
|
+
coin_age += double(coin.out.nValue) * (height - coin.nHeight);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return modified_size ? coin_age / modified_size : 0;
|
|
311
|
+
}
|
|
291
312
|
|
|
292
|
-
// This fills selected_transactions
|
|
293
|
-
chunk_feerate = m_mempool->GetBlockBuilderChunk(selected_transactions);
|
|
294
|
-
FeePerVSize chunk_feerate_vsize = ToFeePerVSize(chunk_feerate);
|
|
313
|
+
struct ResidualEntry : TxGraph::Ref {
|
|
314
|
+
const CTxMemPoolEntry* entry;
|
|
315
|
+
explicit ResidualEntry(const CTxMemPoolEntry* tx_entry) : entry{tx_entry} {}
|
|
316
|
+
};
|
|
317
|
+
} // namespace
|
|
295
318
|
|
|
296
|
-
while (selected_transactions.size() > 0) {
|
|
297
|
-
// Check to see if min fee rate is still respected.
|
|
298
|
-
if (chunk_feerate_vsize << m_options.blockMinFeeRate.GetFeePerVSize()) {
|
|
299
|
-
// Everything else we might consider has a lower feerate
|
|
300
|
-
return;
|
|
319
|
+
void BlockAssembler::addPriorityTxs()
|
|
320
|
+
{
|
|
321
|
+
if (m_options.nBlockPrioritySize == 0) return;
|
|
322
|
+
using Candidate = std::pair<double, const CTxMemPoolEntry*>;
|
|
323
|
+
// Roots priority ties use actual individual fee/vsize, then descending txid.
|
|
324
|
+
const auto compare = [](const Candidate& a, const Candidate& b) {
|
|
325
|
+
if (a.first != b.first) return a.first < b.first;
|
|
326
|
+
const FeePerVSize a_fee{a.second->GetFee(), a.second->GetTxSize()};
|
|
327
|
+
const FeePerVSize b_fee{b.second->GetFee(), b.second->GetTxSize()};
|
|
328
|
+
if (const auto fee_order = FeeRateCompare(a_fee, b_fee); fee_order != 0) return fee_order < 0;
|
|
329
|
+
return a.second->GetTx().GetHash() < b.second->GetTx().GetHash();
|
|
330
|
+
};
|
|
331
|
+
std::vector<Candidate> candidates;
|
|
332
|
+
std::map<const CTxMemPoolEntry*, double> waiting;
|
|
333
|
+
candidates.reserve(m_mempool->mapTx.size());
|
|
334
|
+
for (const auto& entry : m_mempool->mapTx) {
|
|
335
|
+
candidates.emplace_back(CoinAgePriority(entry, m_chainstate.CoinsTip(), nHeight), &entry);
|
|
336
|
+
}
|
|
337
|
+
std::make_heap(candidates.begin(), candidates.end(), compare);
|
|
338
|
+
while (!candidates.empty()) {
|
|
339
|
+
const auto [priority, entry] = candidates.front();
|
|
340
|
+
std::pop_heap(candidates.begin(), candidates.end(), compare);
|
|
341
|
+
candidates.pop_back();
|
|
342
|
+
Assert(!m_in_block.contains(entry));
|
|
343
|
+
const auto parents = m_mempool->GetParents(*entry);
|
|
344
|
+
if (std::ranges::any_of(parents, [&](const auto& parent) { return !m_in_block.contains(&parent.get()); })) {
|
|
345
|
+
waiting.emplace(entry, priority);
|
|
346
|
+
continue;
|
|
301
347
|
}
|
|
302
|
-
|
|
303
|
-
int64_t chunk_sig_ops = 0;
|
|
304
|
-
for (const auto& tx : selected_transactions) {
|
|
305
|
-
chunk_sig_ops += tx.get().GetSigOpCost();
|
|
348
|
+
const uint64_t size = GetSerializeSize(TX_WITH_WITNESS(entry->GetTx()));
|
|
349
|
+
if (!TestChunkBlockLimits({entry->GetModifiedFee(), static_cast<int32_t>(entry->GetAdjustedWeight())}, entry->GetSigOpCost(), size) ||
|
|
350
|
+
!IsFinalTx(entry->GetTx(), nHeight, m_lock_time_cutoff)) continue;
|
|
351
|
+
AddToBlock(*entry);
|
|
352
|
+
// Preserve Roots' threshold behavior: the final selected transaction can
|
|
353
|
+
// cross the area boundary or be the first at/below the priority threshold.
|
|
354
|
+
if (nBlockSize >= m_options.nBlockPrioritySize || priority <= MINIMUM_TX_PRIORITY) break;
|
|
355
|
+
for (const auto& child : m_mempool->GetChildren(*entry)) {
|
|
356
|
+
auto it = waiting.find(&child.get());
|
|
357
|
+
if (it == waiting.end()) continue;
|
|
358
|
+
candidates.emplace_back(it->second, it->first);
|
|
359
|
+
std::push_heap(candidates.begin(), candidates.end(), compare);
|
|
360
|
+
waiting.erase(it);
|
|
306
361
|
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
307
364
|
|
|
308
|
-
// Check to see if this chunk will fit.
|
|
309
|
-
if (!TestChunkBlockLimits(chunk_feerate, chunk_sig_ops) || !TestChunkTransactions(selected_transactions)) {
|
|
310
|
-
// This chunk won't fit, so we skip it and will try the next best one.
|
|
311
|
-
m_mempool->SkipBuilderChunk();
|
|
312
|
-
++nConsecutiveFailed;
|
|
313
|
-
|
|
314
|
-
if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight +
|
|
315
|
-
BLOCK_FULL_ENOUGH_WEIGHT_DELTA > m_options.nBlockMaxWeight) {
|
|
316
|
-
// Give up if we're close to full and haven't succeeded in a while
|
|
317
|
-
return;
|
|
365
|
+
void BlockAssembler::addChunks()
|
|
366
|
+
{
|
|
367
|
+
// Native builders forbid graph mutation while alive. Construct the residual
|
|
368
|
+
// before its builder and destroy the builder before its refs/graph. No live
|
|
369
|
+
// mempool graph mutation, staging, admission or fee update occurs here.
|
|
370
|
+
// The residual is a subset of the admitted graph, retaining L8's aggregate
|
|
371
|
+
// signed fee/weight budgets and cluster bounds. Storage/work is O(mempool)
|
|
372
|
+
// plus native bounded cluster optimization; the disabled/empty-prefix path
|
|
373
|
+
// uses the existing graph directly without copying it.
|
|
374
|
+
std::unique_ptr<TxGraph> residual;
|
|
375
|
+
std::map<Txid, ResidualEntry> refs;
|
|
376
|
+
if (!m_in_block.empty()) {
|
|
377
|
+
residual = MakeTxGraph(m_mempool->m_opts.limits.cluster_count,
|
|
378
|
+
m_mempool->m_opts.limits.cluster_size_vbytes * WITNESS_SCALE_FACTOR,
|
|
379
|
+
ACCEPTABLE_COST, [](const TxGraph::Ref& a, const TxGraph::Ref& b) {
|
|
380
|
+
return static_cast<const ResidualEntry&>(a).entry->GetTx().GetHash() <=>
|
|
381
|
+
static_cast<const ResidualEntry&>(b).entry->GetTx().GetHash();
|
|
382
|
+
});
|
|
383
|
+
for (const auto& entry : m_mempool->mapTx) {
|
|
384
|
+
if (m_in_block.contains(&entry)) continue;
|
|
385
|
+
auto [it, inserted] = refs.try_emplace(entry.GetTx().GetHash(), &entry);
|
|
386
|
+
Assert(inserted);
|
|
387
|
+
residual->AddTransaction(it->second, {entry.GetModifiedFee(), static_cast<int32_t>(entry.GetAdjustedWeight())});
|
|
388
|
+
}
|
|
389
|
+
for (auto& [txid, ref] : refs) {
|
|
390
|
+
for (const auto& parent : m_mempool->GetParents(*ref.entry)) {
|
|
391
|
+
auto it = refs.find(parent.get().GetTx().GetHash());
|
|
392
|
+
if (it != refs.end()) residual->AddDependency(it->second, ref);
|
|
318
393
|
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
auto builder = residual ? residual->GetBlockBuilder() : m_mempool->MakeBlockBuilder();
|
|
397
|
+
const int64_t MAX_CONSECUTIVE_FAILURES = 1000;
|
|
398
|
+
constexpr int32_t BLOCK_FULL_ENOUGH_WEIGHT_DELTA = 4000;
|
|
399
|
+
int64_t consecutive_failed{0};
|
|
400
|
+
while (auto chunk = builder->GetCurrentChunk()) {
|
|
401
|
+
const auto& [chunk_refs, chunk_feerate] = *chunk;
|
|
402
|
+
const FeePerVSize feerate = ToFeePerVSize(chunk_feerate);
|
|
403
|
+
if (feerate << m_options.blockMinFeeRate.GetFeePerVSize()) return;
|
|
404
|
+
std::vector<CTxMemPoolEntryRef> entries;
|
|
405
|
+
int64_t sigops{0};
|
|
406
|
+
uint64_t size{0};
|
|
407
|
+
for (const auto* ref : chunk_refs) {
|
|
408
|
+
const auto& entry = residual ? *static_cast<const ResidualEntry*>(ref)->entry :
|
|
409
|
+
static_cast<const CTxMemPoolEntry&>(*ref);
|
|
410
|
+
entries.emplace_back(entry);
|
|
411
|
+
sigops += entry.GetSigOpCost();
|
|
412
|
+
size += GetSerializeSize(TX_WITH_WITNESS(entry.GetTx()));
|
|
413
|
+
}
|
|
414
|
+
if (!TestChunkBlockLimits(chunk_feerate, sigops, size) || !TestChunkTransactions(entries)) {
|
|
415
|
+
builder->Skip();
|
|
416
|
+
if (++consecutive_failed > MAX_CONSECUTIVE_FAILURES &&
|
|
417
|
+
nBlockWeight + BLOCK_FULL_ENOUGH_WEIGHT_DELTA > m_options.nBlockMaxWeight) return;
|
|
319
418
|
} else {
|
|
320
|
-
m_mempool->IncludeBuilderChunk();
|
|
321
|
-
|
|
322
|
-
// This chunk will fit, so add it to the block.
|
|
323
|
-
nConsecutiveFailed = 0;
|
|
324
|
-
for (const auto& tx : selected_transactions) {
|
|
325
|
-
AddToBlock(tx);
|
|
326
|
-
}
|
|
327
|
-
pblocktemplate->m_package_feerates.emplace_back(chunk_feerate_vsize);
|
|
419
|
+
builder->Include();
|
|
420
|
+
consecutive_failed = 0;
|
|
421
|
+
for (const auto& entry : entries) AddToBlock(entry);
|
|
422
|
+
pblocktemplate->m_package_feerates.emplace_back(feerate);
|
|
328
423
|
}
|
|
329
|
-
|
|
330
|
-
selected_transactions.clear();
|
|
331
|
-
chunk_feerate = m_mempool->GetBlockBuilderChunk(selected_transactions);
|
|
332
|
-
chunk_feerate_vsize = ToFeePerVSize(chunk_feerate);
|
|
333
424
|
}
|
|
334
425
|
}
|
|
335
426
|
|