Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
3001const auto& quality_clusters = clusterset.m_clusters[qual];3002// ... for all clusters in them ...3003for (ClusterSetIndex setindex = 0; setindex < quality_clusters.size(); ++setindex) {3004const auto& cluster = *quality_clusters[setindex];3005live_clusters.insert(&cluster);3006// The number of transactions in a Cluster cannot exceed m_max_cluster_count.3007assert(cluster.GetTxCount() <= m_max_cluster_count);3008// The level must match the Cluster's own idea of what level it is in (but GetLevel3009// can only be called for non-empty Clusters).3010assert(cluster.GetTxCount() == 0 || level == cluster.GetLevel(*this));3011// The sum of their sizes cannot exceed m_max_cluster_size, unless it is an3012// individually oversized transaction singleton. Note that groups of to-be-merged3013// clusters which would exceed this limit are marked oversized, which means they3014// are never applied.3015assert(cluster.IsOversized() || cluster.GetTotalTxSize() <= m_max_cluster_size);3016// OVERSIZED clusters are singletons.3017assert(!cluster.IsOversized() || cluster.GetTxCount() == 1);3018// Transaction counts cannot exceed the Cluster implementation's maximum3019// supported transactions count.3020assert(cluster.GetTxCount() <= cluster.GetMaxTxCount());3021// Unless a Split is yet to be applied, the number of transactions must not be3022// below the Cluster implementation's intended transaction count.3023if (!cluster.NeedsSplitting()) {3024assert(cluster.GetTxCount() >= cluster.GetMinIntendedTxCount());3025}3027// Check the sequence number.3028assert(cluster.m_sequence < m_next_sequence_counter);3029assert(!sequences.contains(cluster.m_sequence));3030sequences.insert(cluster.m_sequence);3031// Remember we saw this Cluster (only if it is non-empty; empty Clusters aren't3032// expected to be referenced by the Entry vector).3033if (cluster.GetTxCount() != 0) {3034actual_clusters.insert(&cluster);3035}3036// Sanity check the cluster, according to the Cluster's internal rules.3037cluster.SanityCheck(*this, level);3038// Check that the cluster's quality and setindex matches its position in the quality list.3039assert(cluster.m_quality == quality);3040assert(cluster.m_setindex == setindex);3041// Count memory usage.3042recomputed_cluster_usage += cluster.TotalMemoryUsage();3043}3044}3046// Verify memory usage.3047assert(clusterset.m_cluster_usage == recomputed_cluster_usage);3049// Verify that all to-be-removed transactions have valid identifiers.3050for (GraphIndex idx : clusterset.m_to_remove) {3051assert(idx < m_entries.size());3052// We cannot assert that all m_to_remove transactions are still present: ~Ref on a3053// (P,M) transaction (present in main, inherited in staging) will cause an m_to_remove3054// addition in both main and staging, but a subsequence ApplyRemovals in main will3055// cause it to disappear from staging too, leaving the m_to_remove in place.3056}3058// Verify that all to-be-added dependencies have valid identifiers.3059for (auto [par_idx, chl_idx] : clusterset.m_deps_to_add) {3060assert(par_idx != chl_idx);3061assert(par_idx < m_entries.size());3062assert(chl_idx < m_entries.size());3063}3065// Verify that the actually encountered clusters match the ones occurring in Entry vector.3066assert(actual_clusters == expected_clusters[level]);3068// Validate m_group_data cluster pointers refer to live clusters at this3069// level or below (staging group data may reference not-yet-pulled-in3070// main clusters).3071if (clusterset.m_group_data.has_value()) {3072for (const Cluster* cl : clusterset.m_group_data->m_group_clusters) {3073assert(live_clusters.contains(cl));3074assert(cl->GetTxCount() > 0);3075assert(FindCluster(cl->GetClusterEntry(0), level) == cl);3076}3077}3078// Verify that the contents of m_removed matches what was expected based on the Entry vector.3079std::set<GraphIndex> actual_removed(clusterset.m_removed.begin(), clusterset.m_removed.end());3080for (auto i : expected_unlinked) {3081// If a transaction exists in both main and staging, and is removed from staging (adding3082// it to m_removed there), and consequently destroyed (wiping the locator completely),3083// it can remain in m_removed despite not having an IsRemoved() locator. Exclude those3084// transactions from the comparison here.3085actual_removed.erase(i);3086expected_removed[level].erase(i);3087}3089assert(actual_removed == expected_removed[level]);3091// If any GraphIndex entries remain in this ClusterSet, compact is not possible.3092if (!clusterset.m_deps_to_add.empty()) compact_possible = false;3093if (!clusterset.m_to_remove.empty()) compact_possible = false;3094if (!clusterset.m_removed.empty()) compact_possible = false;3096// For non-top levels, m_oversized must be known (as it cannot change until the level3097// on top is gone).3098if (level < GetTopLevel()) assert(clusterset.m_oversized.has_value());3099}3101// Verify that the contents of m_unlinked matches what was expected based on the Entry vector.3102std::set<GraphIndex> actual_unlinked(m_unlinked.begin(), m_unlinked.end());3103assert(actual_unlinked == expected_unlinked);3105// If compaction was possible, it should have been performed already, and m_unlinked must be3106// empty (to prevent memory leaks due to an ever-growing m_entries vector).3107if (compact_possible) {3108assert(actual_unlinked.empty());3109}3111// Finally, check the chunk index.3112std::set<GraphIndex> actual_chunkindex;3113FeeFrac last_chunk_feerate;3114for (const auto& chunk : m_main_chunkindex) {3115GraphIndex idx = chunk.m_graph_index;3116actual_chunkindex.insert(idx);3117auto chunk_feerate = m_entries[idx].m_main_chunk_feerate;3118if (!last_chunk_feerate.IsEmpty()) {3119assert(ByRatio{last_chunk_feerate} >= ByRatio{FeeFrac{chunk_feerate}});3120}3121last_chunk_feerate = chunk_feerate;3122}3123assert(actual_chunkindex == expected_chunkindex);3124}3126bool TxGraphImpl::DoWork(uint64_t max_cost) noexcept3127{3128uint64_t cost_done{0};3129// First linearize everything in NEEDS_RELINEARIZE to an acceptable level. If more budget3130// remains after that, try to make everything optimal.3131for (QualityLevel quality : {QualityLevel::NEEDS_FIX, QualityLevel::NEEDS_RELINEARIZE, QualityLevel::ACCEPTABLE}) {3132// First linearize staging, if it exists, then main.3133for (int level = GetTopLevel(); level >= 0; --level) {3134// Do not modify main if it has any observers.3135if (level == 0 && m_main_chunkindex_observers != 0) continue;3136ApplyDependencies(level);3137auto& clusterset = GetClusterSet(level);3138// Do not modify oversized levels.3139if (clusterset.m_oversized == true) continue;3140auto& queue = clusterset.m_clusters[int(quality)];3141while (!queue.empty()) {3142if (cost_done >= max_cost) return false;3143// Randomize the order in which we process, so that if the first cluster somehow3144// needs more work than what max_cost allows, we don't keep spending it on the same3145// one.3146auto pos = m_rng.randrange<size_t>(queue.size());3147auto cost_now = max_cost - cost_done;3148if (quality == QualityLevel::NEEDS_FIX || quality == QualityLevel::NEEDS_RELINEARIZE) {3149// If we're working with clusters that need relinearization still, only perform3150// up to m_acceptable_cost work. If they become ACCEPTABLE, and we still3151// have budget after all other clusters are ACCEPTABLE too, we'll spend the3152// remaining budget on trying to make them OPTIMAL.3153cost_now = std::min(cost_now, m_acceptable_cost);3154}3155auto [cost, improved] = queue[pos].get()->Relinearize(*this, level, cost_now);3156cost_done += cost;3157// If no improvement was made to the Cluster, it means we've essentially run out of3158// budget. Even though it may be the case that cost_done < max_cost still, the3159// linearizer decided there wasn't enough budget left to attempt anything with.3160// To avoid an infinite loop that keeps trying clusters with minuscule budgets,3161// stop here too.3162if (!improved) return false;3163}3164}3165}3166// All possible work has been performed, so we can return true. Note that this does *not* mean3167// that all clusters are optimally linearized now. It may be that there is nothing to do left3168// because all non-optimal clusters are in oversized and/or observer-bearing levels.3169return true;3170}3172void BlockBuilderImpl::Next() noexcept3173{3174// Don't do anything if we're already done.3175if (m_cur_iter == m_graph->m_main_chunkindex.end()) return;3176while (true) {3177// Advance the pointer, and stop if we reach the end.3178++m_cur_iter;3179m_cur_cluster = nullptr;3180if (m_cur_iter == m_graph->m_main_chunkindex.end()) break;3181// Find the cluster pointed to by m_cur_iter.3182const auto& chunk_data = *m_cur_iter;3183const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];3184m_cur_cluster = chunk_end_entry.m_locator[0].cluster;3185m_known_end_of_cluster = false;3186// If we previously skipped a chunk from this cluster we cannot include more from it.3187if (!m_excluded_clusters.contains(m_cur_cluster->m_sequence)) break;3188}3189}3191std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> BlockBuilderImpl::GetCurrentChunk() noexcept3192{3193std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> ret;3194// Populate the return value if we are not done.3195if (m_cur_iter != m_graph->m_main_chunkindex.end()) {3196ret.emplace();3197const auto& chunk_data = *m_cur_iter;3198const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];3199if (chunk_data.m_chunk_count == LinearizationIndex(-1)) {3200// Special case in case just a single transaction remains, avoiding the need to3201// dispatch to and dereference Cluster.3202ret->first.resize(1);3203Assume(chunk_end_entry.m_ref != nullptr);3204ret->first[0] = chunk_end_entry.m_ref;3205m_known_end_of_cluster = true;3206} else {3207Assume(m_cur_cluster);3208ret->first.resize(chunk_data.m_chunk_count);3209auto start_pos = chunk_end_entry.m_main_lin_index + 1 - chunk_data.m_chunk_count;3210m_known_end_of_cluster = m_cur_cluster->GetClusterRefs(*m_graph, ret->first, start_pos);3211// If the chunk size was 1 and at end of cluster, then the special case above should3212// have been used.3213Assume(!m_known_end_of_cluster || chunk_data.m_chunk_count > 1);3214}3215ret->second = chunk_end_entry.m_main_chunk_feerate;3216}3217return ret;3218}3220BlockBuilderImpl::BlockBuilderImpl(TxGraphImpl& graph) noexcept : m_graph(&graph)3221{3222// Make sure all clusters in main are up to date, and acceptable.3223m_graph->MakeAllAcceptable(0);3224// There cannot remain any inapplicable dependencies (only possible if main is oversized).3225Assume(m_graph->m_main_clusterset.m_deps_to_add.empty());3226// Remember that this object is observing the graph's index, so that we can detect concurrent3227// modifications.3228++m_graph->m_main_chunkindex_observers;3229// Find the first chunk.3230m_cur_iter = m_graph->m_main_chunkindex.begin();3231m_cur_cluster = nullptr;3232if (m_cur_iter != m_graph->m_main_chunkindex.end()) {3233// Find the cluster pointed to by m_cur_iter.3234const auto& chunk_data = *m_cur_iter;3235const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];3236m_cur_cluster = chunk_end_entry.m_locator[0].cluster;3237}3238}3240BlockBuilderImpl::~BlockBuilderImpl()3241{3242Assume(m_graph->m_main_chunkindex_observers > 0);3243// Permit modifications to the main graph again after destroying the BlockBuilderImpl.3244--m_graph->m_main_chunkindex_observers;3245}3247void BlockBuilderImpl::Include() noexcept3248{3249// The actual inclusion of the chunk is done by the calling code. All we have to do is switch3250// to the next chunk.
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.