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.
1251m_suboptimal_chunks.emplace_back(chunk_idx);1252// Randomize the initial order of suboptimal chunks in the queue.1253SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size());1254if (j != m_suboptimal_chunks.size() - 1) {1255std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]);1256}1257}1258unsigned chunks = m_chunk_idxs.Count();1259unsigned steps = 0;1260while (!m_suboptimal_chunks.empty()) {1261++steps;1262// Pop an entry from the potentially-suboptimal chunk queue.1263SetIdx chunk_idx = m_suboptimal_chunks.front();1264m_suboptimal_chunks.pop_front();1265Assume(m_suboptimal_idxs[chunk_idx]);1266m_suboptimal_idxs.Reset(chunk_idx);1267// If what was popped is not currently a chunk, continue. This may1268// happen when it was merged with something else since being added.1269if (!m_chunk_idxs[chunk_idx]) continue;1270/** What direction(s) to attempt merging in. 1=up, 2=down, 3=both. */1271unsigned direction = merged_chunks[chunk_idx] ? 3 : init_dir + 1;1272int flip = m_rng.randbool();1273for (int i = 0; i < 2; ++i) {1274if (i ^ flip) {1275if (!(direction & 1)) continue;1276// Attempt to merge the chunk upwards.1277auto result_up = MergeStep<false>(chunk_idx);1278if (result_up != INVALID_SET_IDX) {1279if (!m_suboptimal_idxs[result_up]) {1280m_suboptimal_idxs.Set(result_up);1281m_suboptimal_chunks.push_back(result_up);1282}1283merged_chunks.Set(result_up);1284break;1285}1286} else {1287if (!(direction & 2)) continue;1288// Attempt to merge the chunk downwards.1289auto result_down = MergeStep<true>(chunk_idx);1290if (result_down != INVALID_SET_IDX) {1291if (!m_suboptimal_idxs[result_down]) {1292m_suboptimal_idxs.Set(result_down);1293m_suboptimal_chunks.push_back(result_down);1294}1295merged_chunks.Set(result_down);1296break;1297}1298}1299}1300}1301m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps);1302}1304/** Initialize the data structure for optimization. It must be topological already. */1305void StartOptimizing() noexcept1306{1307m_cost.StartOptimizingBegin();1308Assume(m_suboptimal_chunks.empty());1309// Mark chunks suboptimal.1310m_suboptimal_idxs = m_chunk_idxs;1311for (auto chunk_idx : m_chunk_idxs) {1312m_suboptimal_chunks.push_back(chunk_idx);1313// Randomize the initial order of suboptimal chunks in the queue.1314SetIdx j = m_rng.randrange<SetIdx>(m_suboptimal_chunks.size());1315if (j != m_suboptimal_chunks.size() - 1) {1316std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]);1317}1318}1319m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size());1320}1322/** Try to improve the forest. Returns false if it is optimal, true otherwise. */1323bool OptimizeStep() noexcept1324{1325auto chunk_idx = PickChunkToOptimize();1326if (chunk_idx == INVALID_SET_IDX) {1327// No improvable chunk was found, we are done.1328return false;1329}1330auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx);1331if (parent_idx == TxIdx(-1)) {1332// Nothing to improve in chunk_idx. Need to continue with other chunks, if any.1333return !m_suboptimal_chunks.empty();1334}1335// Deactivate the found dependency and then make the state topological again with a1336// sequence of merges.1337Improve(parent_idx, child_idx);1338return true;1339}1341/** Initialize data structure for minimizing the chunks. Can only be called if state is known1342* to be optimal. OptimizeStep() cannot be called anymore afterwards. */1343void StartMinimizing() noexcept1344{1345m_cost.StartMinimizingBegin();1346m_nonminimal_chunks.clear();1347m_nonminimal_chunks.reserve(m_transaction_idxs.Count());1348// Gather all chunks, and for each, add it with a random pivot in it, and a random initial1349// direction, to m_nonminimal_chunks.1350for (auto chunk_idx : m_chunk_idxs) {1351TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions);1352m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, m_rng.randbits<1>());1353// Randomize the initial order of nonminimal chunks in the queue.1354SetIdx j = m_rng.randrange<SetIdx>(m_nonminimal_chunks.size());1355if (j != m_nonminimal_chunks.size() - 1) {1356std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[j]);1357}1358}1359m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size());1360}1362/** Try to reduce a chunk's size. Returns false if all chunks are minimal, true otherwise. */1363bool MinimizeStep() noexcept1364{1365// If the queue of potentially-non-minimal chunks is empty, we are done.1366if (m_nonminimal_chunks.empty()) return false;1367m_cost.MinimizeStepBegin();1368// Pop an entry from the potentially-non-minimal chunk queue.1369auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front();1370m_nonminimal_chunks.pop_front();1371auto& chunk_info = m_set_info[chunk_idx];1372/** Whether to move the pivot down rather than up. */1373bool move_pivot_down = flags & 1;1374/** Whether this is already the second stage. */1375bool second_stage = flags & 2;1377// Find a random dependency whose top and bottom set feerates are equal, and which has1378// pivot in bottom set (if move_pivot_down) or in top set (if !move_pivot_down).1379std::pair<TxIdx, TxIdx> candidate_dep;1380uint64_t candidate_tiebreak{0};1381bool have_any = false;1382// Iterate over all transactions.1383for (auto tx_idx : chunk_info.transactions) {1384const auto& tx_data = m_tx_data[tx_idx];1385// Iterate over all active child dependencies of the transaction.1386for (auto child_idx : tx_data.active_children) {1387const auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]];1388// Skip if this dependency does not have equal top and bottom set feerates. Note1389// that the top cannot have higher feerate than the bottom, or OptimizeSteps would1390// have dealt with it.1391if (ByRatio{dep_top_info.feerate} < ByRatio{chunk_info.feerate}) continue;1392have_any = true;1393// Skip if this dependency does not have pivot in the right place.1394if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue;1395// Remember this as our chosen dependency if it has a better tiebreak.1396uint64_t tiebreak = m_rng.rand64() | 1;1397if (tiebreak > candidate_tiebreak) {1398candidate_tiebreak = tiebreak;1399candidate_dep = {tx_idx, child_idx};1400}1401}1402}1403m_cost.MinimizeStepMid(/*num_txns=*/chunk_info.transactions.Count());1404// If no dependencies have equal top and bottom set feerate, this chunk is minimal.1405if (!have_any) return true;1406// If all found dependencies have the pivot in the wrong place, try moving it in the other1407// direction. If this was the second stage already, we are done.1408if (candidate_tiebreak == 0) {1409// Switch to other direction, and to second phase.1410flags ^= 3;1411if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags);1412return true;1413}1415// Otherwise, deactivate the dependency that was found.1416auto [parent_chunk_idx, child_chunk_idx] = Deactivate(candidate_dep.first, candidate_dep.second);1417// Determine if there is a dependency from the new bottom to the new top (opposite from the1418// dependency that was just deactivated).1419auto& parent_reachable = m_reachable[parent_chunk_idx].first;1420auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions;1421if (parent_reachable.Overlaps(child_chunk_txn)) {1422// A self-merge is needed. Note that the child_chunk_idx is the top, and1423// parent_chunk_idx is the bottom, because we activate a dependency in the reverse1424// direction compared to the deactivation above.1425auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx);1426// Re-insert the chunk into the queue, in the same direction. Note that the chunk_idx1427// will have changed.1428m_nonminimal_chunks.emplace_back(merged_chunk_idx, pivot_idx, flags);1429m_cost.MinimizeStepEnd(/*split=*/false);1430} else {1431// No self-merge happens, and thus we have found a way to split the chunk. Create two1432// smaller chunks, and add them to the queue. The one that contains the current pivot1433// gets to continue with it in the same direction, to minimize the number of times we1434// alternate direction. If we were in the second phase already, the newly created chunk1435// inherits that too, because we know no split with the pivot on the other side is1436// possible already. The new chunk without the current pivot gets a new randomly-chosen1437// one.1438if (move_pivot_down) {1439auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions);1440m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>());1441m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags);1442} else {1443auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions);1444m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags);1445m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>());1446}1447if (m_rng.randbool()) {1448std::swap(m_nonminimal_chunks.back(), m_nonminimal_chunks[m_nonminimal_chunks.size() - 2]);1449}1450m_cost.MinimizeStepEnd(/*split=*/true);1451}1452return true;1453}1455/** Construct a topologically-valid linearization from the current forest state. Must be1456* topological. fallback_order is a comparator that defines a strong order for DepGraphIndexes1457* in this cluster, used to order equal-feerate transactions and chunks.1458*1459* Specifically, the resulting order consists of:1460* - The chunks of the current SFL state, sorted by (in decreasing order of priority):1461* - topology (parents before children)1462* - highest chunk feerate first1463* - smallest chunk size first1464* - the chunk with the lowest maximum transaction, by fallback_order, first1465* - The transactions within a chunk, sorted by (in decreasing order of priority):1466* - topology (parents before children)1467* - highest tx feerate first1468* - smallest tx size first1469* - the lowest transaction, by fallback_order, first1470*/1471std::vector<DepGraphIndex> GetLinearization(const StrongComparator<DepGraphIndex> auto& fallback_order) noexcept1472{1473m_cost.GetLinearizationBegin();1474/** The output linearization. */1475std::vector<DepGraphIndex> ret;1476ret.reserve(m_set_info.size());1477/** A heap with all chunks (by set index) that can currently be included, sorted by1478* chunk feerate (high to low), chunk size (small to large), and by least maximum element1479* according to the fallback order (which is the second pair element). */1480std::array<std::pair<SetIdx, TxIdx>, SetType::Size()> ready_chunks;1481/** The number of entries of ready_chunks in use. */1482unsigned num_ready_chunks{0};1483/** For every chunk, indexed by SetIdx, the number of unmet dependencies the chunk has on1484* other chunks (not including dependencies within the chunk itself). */1485std::array<TxIdx, SetType::Size()> chunk_deps;1486std::fill_n(chunk_deps.begin(), m_set_info.size(), TxIdx{0});1487/** For every transaction, indexed by TxIdx, the number of unmet dependencies the1488* transaction has. */1489std::array<TxIdx, SetType::Size()> tx_deps;1490std::fill_n(tx_deps.begin(), m_tx_data.size(), TxIdx{0});1491/** A heap with all transactions within the current chunk that can be included, sorted by1492* tx feerate (high to low), tx size (small to large), and fallback order. */1493std::array<TxIdx, SetType::Size()> ready_tx;1494/** The number of entries of ready_tx in use. */1495unsigned num_ready_tx{0};1496// Populate chunk_deps and tx_deps.1497unsigned num_deps{0};1498for (TxIdx chl_idx : m_transaction_idxs) {1499const auto& chl_data = m_tx_data[chl_idx];1500tx_deps[chl_idx] = chl_data.parents.Count();
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.