clusterlin: add more Assumes and sanity checks (tests)
What changed, and why it matters
This commit adds extra internal safety checks and test-like assertions to a Bitcoin Core transaction-cluster linearization module. It does not change normal network behavior; it only helps developers catch programming mistakes during testing or debugging. There is no indication it fixes an active security bug or is exploitable by users.
No urgent action. Treat as routine hardening/test improvement. Reviewers may verify the new invariants are correct and that no existing tests violate them.
Security signals we found
Addition of defensive Assume() and assert() invariants in cluster linearization code
No functional code path changes for production network behavior
No bug class or vulnerability described in commit message or diff
Evidence from the diff
The patch inserts Assume(false) fall-through guards, Assume(m_suboptimal_chunks.empty()) preconditions in MakeTopological() and StartOptimizing(), and additional assert() consistency checks in the cluster_linearize.h self-test code. These verify chunk coverage, disjointness, and dependency-count invariants. Assume() is a debug/test macro that does not affect release builds; assert() is active in debug/test builds. The changes are defensive hardening of internal data-structure invariants, not a fix for a disclosed vulnerability.
Changed components
src/cluster_linearize.hBitcoin Core transaction package/cluster linearization moduleInspect captured patch +16 / −2
diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h
index 882b335f..a4af1641 100644
--- a/src/cluster_linearize.h
+++ b/src/cluster_linearize.h
@@ -865,6 +865,7 @@ private:
--pick;
}
}
+ Assume(false);
break;
}
pick -= count;
@@ -1031,6 +1032,7 @@ public:
/** Make state topological. Can be called after constructing, or after LoadLinearization. */
void MakeTopological() noexcept
{
+ Assume(m_suboptimal_chunks.empty());
for (auto tx : m_transaction_idxs) {
auto& tx_data = m_tx_data[tx];
if (tx_data.chunk_rep == tx) {
@@ -1074,6 +1076,7 @@ public:
/** Initialize the data structure for optimization. It must be topological already. */
void StartOptimizing() noexcept
{
+ Assume(m_suboptimal_chunks.empty());
// Mark chunks suboptimal.
for (auto tx : m_transaction_idxs) {
auto& tx_data = m_tx_data[tx];
@@ -1469,6 +1472,7 @@ public:
//
// Verify the chunks against the list of active dependencies
//
+ SetType chunk_cover;
for (auto tx_idx: m_depgraph.Positions()) {
// Only process chunks for now.
if (m_tx_data[tx_idx].chunk_rep == tx_idx) {
@@ -1478,6 +1482,8 @@ public:
for (auto chunk_tx : chunk_data.chunk_setinfo.transactions) {
assert(m_tx_data[chunk_tx].chunk_rep == tx_idx);
}
+ assert(!chunk_cover.Overlaps(chunk_data.chunk_setinfo.transactions));
+ chunk_cover |= chunk_data.chunk_setinfo.transactions;
// Verify the chunk's transaction set: it must contain the representative, and for
// every active dependency, if it contains the parent or child, it must contain
// both. It must have exactly N-1 active dependencies in it, guaranteeing it is
@@ -1504,6 +1510,8 @@ public:
m_depgraph.FeeRate(chunk_data.chunk_setinfo.transactions));
}
}
+ // Verify that together, the chunks cover all transactions.
+ assert(chunk_cover == m_depgraph.Positions());
//
// Verify other transaction data.
@@ -1539,17 +1547,23 @@ public:
const auto& dep_data = m_dep_data[dep_idx];
// Verify the top_info's transactions: it must contain the parent, and for every
// active dependency, except dep_idx itself, if it contains the parent or child, it
- // must contain both.
+ // must contain both. It must have exactly N-1 active dependencies in it, guaranteeing
+ // it is acyclic.
SetType expected_top = SetType::Singleton(par_idx);
while (true) {
auto old = expected_top;
+ size_t active_dep_count{0};
for (const auto& [par2_idx, chl2_idx, dep2_idx] : active_dependencies) {
if (dep2_idx != dep_idx && (expected_top[par2_idx] || expected_top[chl2_idx])) {
expected_top.Set(par2_idx);
expected_top.Set(chl2_idx);
+ ++active_dep_count;
}
}
- if (old == expected_top) break;
+ if (old == expected_top) {
+ assert(expected_top.Count() == active_dep_count + 1);
+ break;
+ }
}
assert(!expected_top[chl_idx]);
assert(dep_data.top_setinfo.transactions == expected_top);
Why this scored 16/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.