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.
1001return MergeChunks(chunk_idx, merge_chunk_idx);1002} else {1003return MergeChunks(merge_chunk_idx, chunk_idx);1004}1005}1007/** Determine which chunk to merge chunk_idx with, or INVALID_SET_IDX if none. */1008template<bool DownWard>1009SetIdx PickMergeCandidate(SetIdx chunk_idx) noexcept1010{1011m_cost.PickMergeCandidateBegin();1012/** Information about the chunk. */1013Assume(m_chunk_idxs[chunk_idx]);1014auto& chunk_info = m_set_info[chunk_idx];1015// Iterate over all chunks reachable from this one. For those depended-on chunks,1016// remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one.1017// If multiple equal-feerate candidate chunks to merge with exist, pick a random one1018// among them.1020/** The minimum feerate (if downward) or maximum feerate (if upward) to consider when1021* looking for candidate chunks to merge with. Initially, this is the original chunk's1022* feerate, but is updated to be the current best candidate whenever one is found. */1023FeeFrac best_other_chunk_feerate = chunk_info.feerate;1024/** The chunk index for the best candidate chunk to merge with. INVALID_SET_IDX if none. */1025SetIdx best_other_chunk_idx = INVALID_SET_IDX;1026/** We generate random tiebreak values to pick between equal-feerate candidate chunks.1027* This variable stores the tiebreak of the current best candidate. */1028uint64_t best_other_chunk_tiebreak{0};1030/** Which parent/child transactions we still need to process the chunks for. */1031auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first;1032unsigned steps = 0;1033while (todo.Any()) {1034++steps;1035// Find a chunk for a transaction in todo, and remove all its transactions from todo.1036auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx;1037auto& reached_chunk_info = m_set_info[reached_chunk_idx];1038todo -= reached_chunk_info.transactions;1039// See if it has an acceptable feerate.1040auto cmp = DownWard ? ByRatio{best_other_chunk_feerate} <=> ByRatio{reached_chunk_info.feerate}1041: ByRatio{reached_chunk_info.feerate} <=> ByRatio{best_other_chunk_feerate};1042if (cmp > 0) continue;1043uint64_t tiebreak = m_rng.rand64();1044if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) {1045best_other_chunk_feerate = reached_chunk_info.feerate;1046best_other_chunk_idx = reached_chunk_idx;1047best_other_chunk_tiebreak = tiebreak;1048}1049}1050Assume(steps <= m_set_info.size());1052m_cost.PickMergeCandidateEnd(/*num_steps=*/steps);1053return best_other_chunk_idx;1054}1056/** Perform an upward or downward merge step, on the specified chunk. Returns the merged chunk,1057* or INVALID_SET_IDX if no merge took place. */1058template<bool DownWard>1059SetIdx MergeStep(SetIdx chunk_idx) noexcept1060{1061auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx);1062if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX;1063chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx);1064Assume(chunk_idx != INVALID_SET_IDX);1065return chunk_idx;1066}1068/** Perform an upward or downward merge sequence on the specified chunk. */1069template<bool DownWard>1070void MergeSequence(SetIdx chunk_idx) noexcept1071{1072Assume(m_chunk_idxs[chunk_idx]);1073while (true) {1074auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx);1075if (merged_chunk_idx == INVALID_SET_IDX) break;1076chunk_idx = merged_chunk_idx;1077}1078// Add the chunk to the queue of improvable chunks, if it wasn't already there.1079if (!m_suboptimal_idxs[chunk_idx]) {1080m_suboptimal_idxs.Set(chunk_idx);1081m_suboptimal_chunks.push_back(chunk_idx);1082}1083}1085/** Split a chunk, and then merge the resulting two chunks to make the graph topological1086* again. */1087void Improve(TxIdx parent_idx, TxIdx child_idx) noexcept1088{1089// Deactivate the specified dependency, splitting it into two new chunks: a top containing1090// the parent, and a bottom containing the child. The top should have a higher feerate.1091auto [parent_chunk_idx, child_chunk_idx] = Deactivate(parent_idx, child_idx);1093// At this point we have exactly two chunks which may violate topology constraints (the1094// parent chunk and child chunk that were produced by deactivation). We can fix1095// these using just merge sequences, one upwards and one downwards, avoiding the need for a1096// full MakeTopological.1097const auto& parent_reachable = m_reachable[parent_chunk_idx].first;1098const auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions;1099if (parent_reachable.Overlaps(child_chunk_txn)) {1100// The parent chunk has a dependency on a transaction in the child chunk. In this case,1101// the parent needs to merge back with the child chunk (a self-merge), and no other1102// merges are needed. Special-case this, so the overhead of PickMergeCandidate and1103// MergeSequence can be avoided.1105// In the self-merge, the roles reverse: the parent chunk (from the split) depends1106// on the child chunk, so child_chunk_idx is the "top" and parent_chunk_idx is the1107// "bottom" for MergeChunks.1108auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx);1109if (!m_suboptimal_idxs[merged_chunk_idx]) {1110m_suboptimal_idxs.Set(merged_chunk_idx);1111m_suboptimal_chunks.push_back(merged_chunk_idx);1112}1113} else {1114// Merge the top chunk with lower-feerate chunks it depends on.1115MergeSequence<false>(parent_chunk_idx);1116// Merge the bottom chunk with higher-feerate chunks that depend on it.1117MergeSequence<true>(child_chunk_idx);1118}1119}1121/** Determine the next chunk to optimize, or INVALID_SET_IDX if none. */1122SetIdx PickChunkToOptimize() noexcept1123{1124m_cost.PickChunkToOptimizeBegin();1125unsigned steps{0};1126while (!m_suboptimal_chunks.empty()) {1127++steps;1128// Pop an entry from the potentially-suboptimal chunk queue.1129SetIdx chunk_idx = m_suboptimal_chunks.front();1130Assume(m_suboptimal_idxs[chunk_idx]);1131m_suboptimal_idxs.Reset(chunk_idx);1132m_suboptimal_chunks.pop_front();1133if (m_chunk_idxs[chunk_idx]) {1134m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps);1135return chunk_idx;1136}1137// If what was popped is not currently a chunk, continue. This may1138// happen when a split chunk merges in Improve() with one or more existing chunks that1139// are themselves on the suboptimal queue already.1140}1141m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps);1142return INVALID_SET_IDX;1143}1145/** Find a (parent, child) dependency to deactivate in chunk_idx, or (-1, -1) if none. */1146std::pair<TxIdx, TxIdx> PickDependencyToSplit(SetIdx chunk_idx) noexcept1147{1148m_cost.PickDependencyToSplitBegin();1149Assume(m_chunk_idxs[chunk_idx]);1150auto& chunk_info = m_set_info[chunk_idx];1152// Remember the best dependency {par, chl} seen so far.1153std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)};1154uint64_t candidate_tiebreak = 0;1155// Iterate over all transactions.1156for (auto tx_idx : chunk_info.transactions) {1157const auto& tx_data = m_tx_data[tx_idx];1158// Iterate over all active child dependencies of the transaction.1159for (auto child_idx : tx_data.active_children) {1160auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]];1161// Skip if this dependency is ineligible (the top chunk that would be created1162// does not have higher feerate than the chunk it is currently part of).1163auto cmp = ByRatio{dep_top_info.feerate} <=> ByRatio{chunk_info.feerate};1164if (cmp <= 0) continue;1165// Generate a random tiebreak for this dependency, and reject it if its tiebreak1166// is worse than the best so far. This means that among all eligible1167// dependencies, a uniformly random one will be chosen.1168uint64_t tiebreak = m_rng.rand64();1169if (tiebreak < candidate_tiebreak) continue;1170// Remember this as our (new) candidate dependency.1171candidate_dep = {tx_idx, child_idx};1172candidate_tiebreak = tiebreak;1173}1174}1175m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count());1176return candidate_dep;1177}1179public:1180/** Construct a spanning forest for the given DepGraph, with every transaction in its own chunk1181* (not topological). */1182explicit SpanningForestState(const DepGraph<SetType>& depgraph LIFETIMEBOUND, uint64_t rng_seed, const CostModel& cost = CostModel{}) noexcept :1183m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost)1184{1185m_cost.InitializeBegin();1186m_transaction_idxs = depgraph.Positions();1187auto num_transactions = m_transaction_idxs.Count();1188m_tx_data.resize(depgraph.PositionRange());1189m_set_info.resize(num_transactions);1190m_reachable.resize(num_transactions);1191m_suboptimal_chunks.reserve(num_transactions);1192size_t num_chunks = 0;1193size_t num_deps = 0;1194for (auto tx_idx : m_transaction_idxs) {1195// Fill in transaction data.1196auto& tx_data = m_tx_data[tx_idx];1197tx_data.parents = depgraph.GetReducedParents(tx_idx);1198for (auto parent_idx : tx_data.parents) {1199m_tx_data[parent_idx].children.Set(tx_idx);1200}1201num_deps += tx_data.parents.Count();1202// Create a singleton chunk for it.1203tx_data.chunk_idx = num_chunks;1204m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx);1205}1206// Set the reachable transactions for each chunk to the transactions' parents and children.1207for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx) {1208auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()];1209m_reachable[chunk_idx].first = tx_data.parents;1210m_reachable[chunk_idx].second = tx_data.children;1211}1212Assume(num_chunks == num_transactions);1213// Mark all chunk sets as chunks.1214m_chunk_idxs = SetType::Fill(num_chunks);1215m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps);1216}1218/** Load an existing linearization. Must be called immediately after constructor. The result is1219* topological if the linearization is valid. Otherwise, MakeTopological still needs to be1220* called. */1221void LoadLinearization(std::span<const DepGraphIndex> old_linearization) noexcept1222{1223// Add transactions one by one, in order of existing linearization.1224for (DepGraphIndex tx_idx : old_linearization) {1225auto chunk_idx = m_tx_data[tx_idx].chunk_idx;1226// Merge the chunk upwards, as long as merging succeeds.1227while (true) {1228chunk_idx = MergeStep<false>(chunk_idx);1229if (chunk_idx == INVALID_SET_IDX) break;1230}1231}1232}1234/** Make state topological. Can be called after constructing, or after LoadLinearization. */1235void MakeTopological() noexcept1236{1237m_cost.MakeTopologicalBegin();1238Assume(m_suboptimal_chunks.empty());1239/** What direction to initially merge chunks in; one of the two directions is enough. This1240* is sufficient because if a non-topological inactive dependency exists between two1241* chunks, at least one of the two chunks will eventually be processed in a direction that1242* discovers it - either the lower chunk tries upward, or the upper chunk tries downward.1243* Chunks that are the result of the merging are always tried in both directions. */1244unsigned init_dir = m_rng.randbool();1245/** Which chunks are the result of merging, and thus need merge attempts in both1246* directions. */1247SetType merged_chunks;1248// Mark chunks as suboptimal.1249m_suboptimal_idxs = m_chunk_idxs;1250for (auto chunk_idx : m_chunk_idxs) {
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.