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. Before is on the left; after is on the right. Unified view fits narrow screens more easily.
src/node/miner.cpp
CHANGED
|
@@ -28,6 +28,7 @@
|
|
| 28 |
#include <validation.h>
|
| 29 |
|
| 30 |
#include <algorithm>
|
|
|
|
| 31 |
#include <utility>
|
| 32 |
#include <numeric>
|
| 33 |
|
|
@@ -81,6 +82,8 @@ static BlockAssembler::Options ClampOptions(BlockAssembler::Options options)
|
|
| 81 |
// Apply DEFAULT_BLOCK_RESERVED_WEIGHT when the caller left it unset.
|
| 82 |
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 |
options.coinbase_output_max_additional_sigops = std::clamp<size_t>(options.coinbase_output_max_additional_sigops, 0, MAX_BLOCK_SIGOPS_COST);
|
|
|
|
|
|
|
| 84 |
// Limit weight to between block_reserved_weight and MAX_BLOCK_WEIGHT for sanity:
|
| 85 |
// block_reserved_weight can safely exceed -blockmaxweight, but the rest of the block template will be empty.
|
| 86 |
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 |
void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& options)
|
| 99 |
{
|
| 100 |
// Block resource limits
|
| 101 |
-
options.nBlockMaxWeight = args.GetIntArg("-blockmaxweight", options.nBlockMaxWeight);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
if (const auto blockmintxfee{args.GetArg("-blockmintxfee")}) {
|
| 103 |
if (const auto parsed{ParseMoney(*blockmintxfee)}) options.blockMinFeeRate = CFeeRate{*parsed};
|
| 104 |
}
|
|
@@ -111,6 +123,8 @@ void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& optio
|
|
| 111 |
void BlockAssembler::resetBlock()
|
| 112 |
{
|
| 113 |
// Reserve space for fixed-size block header, txs count, and coinbase tx.
|
|
|
|
|
|
|
| 114 |
nBlockWeight = *Assert(m_options.block_reserved_weight);
|
| 115 |
nBlockSigOpsCost = m_options.coinbase_output_max_additional_sigops;
|
| 116 |
|
|
@@ -149,9 +163,8 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock()
|
|
| 149 |
|
| 150 |
if (m_mempool) {
|
| 151 |
LOCK(m_mempool->cs);
|
| 152 |
-
m_mempool->StartBlockBuilding();
|
| 153 |
addChunks();
|
| 154 |
-
m_mempool->StopBlockBuilding();
|
| 155 |
}
|
| 156 |
|
| 157 |
const auto time_1{SteadyClock::now()};
|
|
@@ -236,8 +249,9 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock()
|
|
| 236 |
return std::move(pblocktemplate);
|
| 237 |
}
|
| 238 |
|
| 239 |
-
bool BlockAssembler::TestChunkBlockLimits(FeePerWeight chunk_feerate, int64_t chunk_sigops_cost) const
|
| 240 |
{
|
|
|
|
| 241 |
if (nBlockWeight + chunk_feerate.size >= m_options.nBlockMaxWeight) {
|
| 242 |
return false;
|
| 243 |
}
|
|
@@ -261,6 +275,8 @@ bool BlockAssembler::TestChunkTransactions(const std::vector<CTxMemPoolEntryRef>
|
|
| 261 |
|
| 262 |
void BlockAssembler::AddToBlock(const CTxMemPoolEntry& entry)
|
| 263 |
{
|
|
|
|
|
|
|
| 264 |
pblocktemplate->block.vtx.emplace_back(entry.GetSharedTx());
|
| 265 |
pblocktemplate->vTxFees.push_back(entry.GetFee());
|
| 266 |
pblocktemplate->vTxSigOpsCost.push_back(entry.GetSigOpCost());
|
|
@@ -276,60 +292,135 @@ void BlockAssembler::AddToBlock(const CTxMemPoolEntry& entry)
|
|
| 276 |
}
|
| 277 |
}
|
| 278 |
|
| 279 |
-
void BlockAssembler::addChunks()
|
|
|
|
|
|
|
|
|
|
| 280 |
{
|
| 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;
|
|
|
|
|
|
|
| 291 |
|
| 292 |
-
// This fills selected_transactions
|
| 293 |
-
chunk_feerate = m_mempool->GetBlockBuilderChunk(selected_transactions);
|
| 294 |
-
FeePerVSize chunk_feerate_vsize = ToFeePerVSize(chunk_feerate);
|
|
|
|
|
|
|
| 295 |
|
| 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;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
}
|
| 302 |
-
|
| 303 |
-
int64_t chunk_sig_ops = 0;
|
| 304 |
-
for (const auto& tx : selected_transactions) {
|
| 305 |
-
chunk_sig_ops += tx.get().GetSigOpCost();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
}
|
|
|
|
|
|
|
| 307 |
|
| 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;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
} 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);
|
| 328 |
}
|
| 329 |
-
|
| 330 |
-
selected_transactions.clear();
|
| 331 |
-
chunk_feerate = m_mempool->GetBlockBuilderChunk(selected_transactions);
|
| 332 |
-
chunk_feerate_vsize = ToFeePerVSize(chunk_feerate);
|
| 333 |
}
|
| 334 |
}
|
| 335 |
|
|
|
|
| 28 |
#include <validation.h>
|
| 29 |
|
| 30 |
#include <algorithm>
|
| 31 |
+
#include <map>
|
| 32 |
#include <utility>
|
| 33 |
#include <numeric>
|
| 34 |
|
|
|
|
| 82 |
// Apply DEFAULT_BLOCK_RESERVED_WEIGHT when the caller left it unset.
|
| 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);
|
| 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);
|
| 87 |
// Limit weight to between block_reserved_weight and MAX_BLOCK_WEIGHT for sanity:
|
| 88 |
// block_reserved_weight can safely exceed -blockmaxweight, but the rest of the block template will be empty.
|
| 89 |
options.nBlockMaxWeight = std::clamp<size_t>(options.nBlockMaxWeight, *options.block_reserved_weight, MAX_BLOCK_WEIGHT);
|
|
|
|
| 101 |
void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& options)
|
| 102 |
{
|
| 103 |
// Block resource limits
|
| 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);
|
| 114 |
if (const auto blockmintxfee{args.GetArg("-blockmintxfee")}) {
|
| 115 |
if (const auto parsed{ParseMoney(*blockmintxfee)}) options.blockMinFeeRate = CFeeRate{*parsed};
|
| 116 |
}
|
|
|
|
| 123 |
void BlockAssembler::resetBlock()
|
| 124 |
{
|
| 125 |
// Reserve space for fixed-size block header, txs count, and coinbase tx.
|
| 126 |
+
m_in_block.clear();
|
| 127 |
+
nBlockSize = DEFAULT_BLOCK_RESERVED_SIZE;
|
| 128 |
nBlockWeight = *Assert(m_options.block_reserved_weight);
|
| 129 |
nBlockSigOpsCost = m_options.coinbase_output_max_additional_sigops;
|
| 130 |
|
|
|
|
| 163 |
|
| 164 |
if (m_mempool) {
|
| 165 |
LOCK(m_mempool->cs);
|
| 166 |
+
addPriorityTxs();
|
| 167 |
addChunks();
|
|
|
|
| 168 |
}
|
| 169 |
|
| 170 |
const auto time_1{SteadyClock::now()};
|
|
|
|
| 249 |
return std::move(pblocktemplate);
|
| 250 |
}
|
| 251 |
|
| 252 |
+
bool BlockAssembler::TestChunkBlockLimits(FeePerWeight chunk_feerate, int64_t chunk_sigops_cost, uint64_t chunk_size) const
|
| 253 |
{
|
| 254 |
+
if (nBlockSize + chunk_size >= m_options.nBlockMaxSize) return false;
|
| 255 |
if (nBlockWeight + chunk_feerate.size >= m_options.nBlockMaxWeight) {
|
| 256 |
return false;
|
| 257 |
}
|
|
|
|
| 275 |
|
| 276 |
void BlockAssembler::AddToBlock(const CTxMemPoolEntry& entry)
|
| 277 |
{
|
| 278 |
+
Assert(m_in_block.insert(&entry).second);
|
| 279 |
+
nBlockSize += GetSerializeSize(TX_WITH_WITNESS(entry.GetTx()));
|
| 280 |
pblocktemplate->block.vtx.emplace_back(entry.GetSharedTx());
|
| 281 |
pblocktemplate->vTxFees.push_back(entry.GetFee());
|
| 282 |
pblocktemplate->vTxSigOpsCost.push_back(entry.GetSigOpCost());
|
|
|
|
| 292 |
}
|
| 293 |
}
|
| 294 |
|
| 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)
|
| 299 |
{
|
| 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 |
+
}
|
| 312 |
|
| 313 |
+
struct ResidualEntry : TxGraph::Ref {
|
| 314 |
+
const CTxMemPoolEntry* entry;
|
| 315 |
+
explicit ResidualEntry(const CTxMemPoolEntry* tx_entry) : entry{tx_entry} {}
|
| 316 |
+
};
|
| 317 |
+
} // namespace
|
| 318 |
|
| 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;
|
| 347 |
}
|
| 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);
|
| 361 |
}
|
| 362 |
+
}
|
| 363 |
+
}
|
| 364 |
|
| 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);
|
| 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;
|
| 418 |
} else {
|
| 419 |
+
builder->Include();
|
| 420 |
+
consecutive_failed = 0;
|
| 421 |
+
for (const auto& entry : entries) AddToBlock(entry);
|
| 422 |
+
pblocktemplate->m_package_feerates.emplace_back(feerate);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 424 |
}
|
| 425 |
}
|
| 426 |
|