@@ -254,6 +254,71 @@ class TxGraphImpl final : public TxGraph
254254 /* * Next sequence number to assign to created Clusters. */
255255 uint64_t m_next_sequence_counter{0 };
256256
257+ /* * Information about a chunk in the main graph. */
258+ struct ChunkData
259+ {
260+ /* * The Entry which is the last transaction of the chunk. */
261+ mutable GraphIndex m_graph_index;
262+ /* * How many transactions the chunk contains. */
263+ LinearizationIndex m_chunk_count;
264+
265+ ChunkData (GraphIndex graph_index, LinearizationIndex chunk_count) noexcept :
266+ m_graph_index{graph_index}, m_chunk_count{chunk_count} {}
267+ };
268+
269+ /* * Compare two Cluster* by their m_sequence value (while supporting nullptr). */
270+ static std::strong_ordering CompareClusters (Cluster* a, Cluster* b) noexcept
271+ {
272+ // The nullptr pointer compares before everything else.
273+ if (a == nullptr || b == nullptr ) {
274+ return (a != nullptr ) <=> (b != nullptr );
275+ }
276+ // If neither pointer is nullptr, compare the Clusters' sequence numbers.
277+ Assume (a == b || a->m_sequence != b->m_sequence );
278+ return a->m_sequence <=> b->m_sequence ;
279+ }
280+
281+ /* * Compare two entries (which must both exist within the main graph). */
282+ std::strong_ordering CompareMainTransactions (GraphIndex a, GraphIndex b) const noexcept
283+ {
284+ Assume (a < m_entries.size () && b < m_entries.size ());
285+ const auto & entry_a = m_entries[a];
286+ const auto & entry_b = m_entries[b];
287+ // Compare chunk feerates, and return result if it differs.
288+ auto feerate_cmp = FeeRateCompare (entry_b.m_main_chunk_feerate , entry_a.m_main_chunk_feerate );
289+ if (feerate_cmp < 0 ) return std::strong_ordering::less;
290+ if (feerate_cmp > 0 ) return std::strong_ordering::greater;
291+ // Compare Cluster m_sequence as tie-break for equal chunk feerates.
292+ const auto & locator_a = entry_a.m_locator [0 ];
293+ const auto & locator_b = entry_b.m_locator [0 ];
294+ Assume (locator_a.IsPresent () && locator_b.IsPresent ());
295+ if (locator_a.cluster != locator_b.cluster ) {
296+ return CompareClusters (locator_a.cluster , locator_b.cluster );
297+ }
298+ // As final tie-break, compare position within cluster linearization.
299+ return entry_a.m_main_lin_index <=> entry_b.m_main_lin_index ;
300+ }
301+
302+ /* * Comparator for ChunkData objects in mining order. */
303+ class ChunkOrder
304+ {
305+ const TxGraphImpl* const m_graph;
306+ public:
307+ explicit ChunkOrder (const TxGraphImpl* graph) : m_graph(graph) {}
308+
309+ bool operator ()(const ChunkData& a, const ChunkData& b) const noexcept
310+ {
311+ return m_graph->CompareMainTransactions (a.m_graph_index , b.m_graph_index ) < 0 ;
312+ }
313+ };
314+
315+ /* * Definition for the mining index type. */
316+ using ChunkIndex = std::set<ChunkData, ChunkOrder>;
317+
318+ /* * Index of ChunkData objects, indexing the last transaction in each chunk in the main
319+ * graph. */
320+ ChunkIndex m_main_chunkindex;
321+
257322 /* * A Locator that describes whether, where, and in which Cluster an Entry appears.
258323 * Every Entry has MAX_LEVELS locators, as it may appear in one Cluster per level.
259324 *
@@ -310,6 +375,9 @@ class TxGraphImpl final : public TxGraph
310375 {
311376 /* * Pointer to the corresponding Ref object if any, or nullptr if unlinked. */
312377 Ref* m_ref{nullptr };
378+ /* * Iterator to the corresponding ChunkData, if any, and m_main_chunkindex.end() otherwise.
379+ * This is initialized on construction of the Entry, in AddTransaction. */
380+ ChunkIndex::iterator m_main_chunkindex_iterator;
313381 /* * Which Cluster and position therein this Entry appears in. ([0] = main, [1] = staged). */
314382 Locator m_locator[MAX_LEVELS];
315383 /* * The chunk feerate of this transaction in main (if present in m_locator[0]). */
@@ -324,43 +392,11 @@ class TxGraphImpl final : public TxGraph
324392 /* * Set of Entries which have no linked Ref anymore. */
325393 std::vector<GraphIndex> m_unlinked;
326394
327- /* * Compare two Cluster* by their m_sequence value (while supporting nullptr). */
328- static std::strong_ordering CompareClusters (Cluster* a, Cluster* b) noexcept
329- {
330- // The nullptr pointer compares before everything else.
331- if (a == nullptr || b == nullptr ) {
332- return (a != nullptr ) <=> (b != nullptr );
333- }
334- // If neither pointer is nullptr, compare the Clusters' sequence numbers.
335- Assume (a == b || a->m_sequence != b->m_sequence );
336- return a->m_sequence <=> b->m_sequence ;
337- }
338-
339- /* * Compare two entries (which must both exist within the main graph). */
340- std::strong_ordering CompareMainTransactions (GraphIndex a, GraphIndex b) const noexcept
341- {
342- Assume (a < m_entries.size () && b < m_entries.size ());
343- const auto & entry_a = m_entries[a];
344- const auto & entry_b = m_entries[b];
345- // Compare chunk feerates, and return result if it differs.
346- auto feerate_cmp = FeeRateCompare (entry_b.m_main_chunk_feerate , entry_a.m_main_chunk_feerate );
347- if (feerate_cmp < 0 ) return std::strong_ordering::less;
348- if (feerate_cmp > 0 ) return std::strong_ordering::greater;
349- // Compare Cluster m_sequence as tie-break for equal chunk feerates.
350- const auto & locator_a = entry_a.m_locator [0 ];
351- const auto & locator_b = entry_b.m_locator [0 ];
352- Assume (locator_a.IsPresent () && locator_b.IsPresent ());
353- if (locator_a.cluster != locator_b.cluster ) {
354- return CompareClusters (locator_a.cluster , locator_b.cluster );
355- }
356- // As final tie-break, compare position within cluster linearization.
357- return entry_a.m_main_lin_index <=> entry_b.m_main_lin_index ;
358- }
359-
360395public:
361396 /* * Construct a new TxGraphImpl with the specified maximum cluster count. */
362397 explicit TxGraphImpl (DepGraphIndex max_cluster_count) noexcept :
363- m_max_cluster_count(max_cluster_count)
398+ m_max_cluster_count(max_cluster_count),
399+ m_main_chunkindex(ChunkOrder(this ))
364400 {
365401 Assume (max_cluster_count >= 1 );
366402 Assume (max_cluster_count <= MAX_CLUSTER_COUNT_LIMIT);
@@ -544,13 +580,23 @@ void TxGraphImpl::ClearLocator(int level, GraphIndex idx) noexcept
544580 --m_staging_clusterset->m_txcount ;
545581 }
546582 }
583+ if (level == 0 && entry.m_main_chunkindex_iterator != m_main_chunkindex.end ()) {
584+ m_main_chunkindex.erase (entry.m_main_chunkindex_iterator );
585+ entry.m_main_chunkindex_iterator = m_main_chunkindex.end ();
586+ }
547587}
548588
549589void Cluster::Updated (TxGraphImpl& graph) noexcept
550590{
551591 // Update all the Locators for this Cluster's Entry objects.
552592 for (DepGraphIndex idx : m_linearization) {
553593 auto & entry = graph.m_entries [m_mapping[idx]];
594+ if (m_level == 0 && entry.m_main_chunkindex_iterator != graph.m_main_chunkindex .end ()) {
595+ // Destroy any potential ChunkData prior to modifying the Cluster (as that could
596+ // invalidate its ordering).
597+ graph.m_main_chunkindex .erase (entry.m_main_chunkindex_iterator );
598+ entry.m_main_chunkindex_iterator = graph.m_main_chunkindex .end ();
599+ }
554600 entry.m_locator [m_level].SetPresent (this , idx);
555601 }
556602 // If this is for the main graph (level = 0), and the Cluster's quality is ACCEPTABLE or
@@ -559,22 +605,30 @@ void Cluster::Updated(TxGraphImpl& graph) noexcept
559605 // ACCEPTABLE, so it is pointless to compute these if we haven't reached that quality level
560606 // yet.
561607 if (m_level == 0 && IsAcceptable ()) {
562- LinearizationChunking chunking (m_depgraph, m_linearization);
608+ const LinearizationChunking chunking (m_depgraph, m_linearization);
563609 LinearizationIndex lin_idx{0 };
564610 // Iterate over the chunks.
565611 for (unsigned chunk_idx = 0 ; chunk_idx < chunking.NumChunksLeft (); ++chunk_idx) {
566612 auto chunk = chunking.GetChunk (chunk_idx);
567- Assume (chunk.transactions .Any ());
613+ const auto chunk_count = chunk.transactions .Count ();
614+ Assume (chunk_count > 0 );
568615 // Iterate over the transactions in the linearization, which must match those in chunk.
569- do {
616+ while ( true ) {
570617 DepGraphIndex idx = m_linearization[lin_idx];
571618 GraphIndex graph_idx = m_mapping[idx];
572619 auto & entry = graph.m_entries [graph_idx];
573620 entry.m_main_lin_index = lin_idx++;
574621 entry.m_main_chunk_feerate = FeePerWeight::FromFeeFrac (chunk.feerate );
575622 Assume (chunk.transactions [idx]);
576623 chunk.transactions .Reset (idx);
577- } while (chunk.transactions .Any ());
624+ if (chunk.transactions .None ()) {
625+ // Last transaction in the chunk.
626+ auto [it, inserted] = graph.m_main_chunkindex .emplace (graph_idx, chunk_count);
627+ Assume (inserted);
628+ entry.m_main_chunkindex_iterator = it;
629+ break ;
630+ }
631+ }
578632 }
579633 }
580634}
@@ -829,7 +883,14 @@ void Cluster::Merge(TxGraphImpl& graph, Cluster& other) noexcept
829883 // Update the transaction's Locator. There is no need to call Updated() to update chunk
830884 // feerates, as Updated() will be invoked by Cluster::ApplyDependencies on the resulting
831885 // merged Cluster later anyway).
832- graph.m_entries [idx].m_locator [m_level].SetPresent (this , new_pos);
886+ auto & entry = graph.m_entries [idx];
887+ if (m_level == 0 && entry.m_main_chunkindex_iterator != graph.m_main_chunkindex .end ()) {
888+ // Destroy any potential ChunkData prior to modifying the Cluster (as that could
889+ // invalidate its ordering).
890+ graph.m_main_chunkindex .erase (entry.m_main_chunkindex_iterator );
891+ entry.m_main_chunkindex_iterator = graph.m_main_chunkindex .end ();
892+ }
893+ entry.m_locator [m_level].SetPresent (this , new_pos);
833894 }
834895 // Purge the other Cluster, now that everything has been moved.
835896 other.m_depgraph = DepGraph<SetType>{};
@@ -1043,6 +1104,10 @@ void TxGraphImpl::SwapIndexes(GraphIndex a, GraphIndex b) noexcept
10431104 Entry& entry = m_entries[idx];
10441105 // Update linked Ref, if any exists.
10451106 if (entry.m_ref ) GetRefIndex (*entry.m_ref ) = idx;
1107+ // Update linked chunk index entries, if any exist.
1108+ if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end ()) {
1109+ entry.m_main_chunkindex_iterator ->m_graph_index = idx;
1110+ }
10461111 // Update the locators for both levels. The rest of the Entry information will not change,
10471112 // so no need to invoke Cluster::Updated().
10481113 for (int level = 0 ; level < MAX_LEVELS; ++level) {
@@ -1452,6 +1517,7 @@ TxGraph::Ref TxGraphImpl::AddTransaction(const FeePerWeight& feerate) noexcept
14521517 auto idx = m_entries.size ();
14531518 m_entries.emplace_back ();
14541519 auto & entry = m_entries.back ();
1520+ entry.m_main_chunkindex_iterator = m_main_chunkindex.end ();
14551521 entry.m_ref = &ret;
14561522 GetRefGraph (ret) = this ;
14571523 GetRefIndex (ret) = idx;
@@ -1983,6 +2049,7 @@ void Cluster::SanityCheck(const TxGraphImpl& graph, int level) const
19832049 // Verify m_linearization.
19842050 SetType m_done;
19852051 LinearizationIndex linindex{0 };
2052+ DepGraphIndex chunk_pos{0 }; // !< position within the current chunk
19862053 assert (m_depgraph.IsAcyclic ());
19872054 for (auto lin_pos : m_linearization) {
19882055 assert (lin_pos < m_mapping.size ());
@@ -1999,8 +2066,17 @@ void Cluster::SanityCheck(const TxGraphImpl& graph, int level) const
19992066 ++linindex;
20002067 if (!linchunking.GetChunk (0 ).transactions [lin_pos]) {
20012068 linchunking.MarkDone (linchunking.GetChunk (0 ).transactions );
2069+ chunk_pos = 0 ;
20022070 }
20032071 assert (entry.m_main_chunk_feerate == linchunking.GetChunk (0 ).feerate );
2072+ // Verify that an entry in the chunk index exists for every chunk-ending transaction.
2073+ ++chunk_pos;
2074+ bool is_chunk_end = (chunk_pos == linchunking.GetChunk (0 ).transactions .Count ());
2075+ assert ((entry.m_main_chunkindex_iterator != graph.m_main_chunkindex .end ()) == is_chunk_end);
2076+ if (is_chunk_end) {
2077+ auto & chunk_data = *entry.m_main_chunkindex_iterator ;
2078+ assert (chunk_data.m_chunk_count == chunk_pos);
2079+ }
20042080 // If this Cluster has an acceptable quality level, its chunks must be connected.
20052081 assert (m_depgraph.IsConnected (linchunking.GetChunk (0 ).transactions ));
20062082 }
@@ -2019,6 +2095,8 @@ void TxGraphImpl::SanityCheck() const
20192095 std::set<GraphIndex> expected_removed[MAX_LEVELS];
20202096 /* * Which Cluster::m_sequence values have been encountered. */
20212097 std::set<uint64_t > sequences;
2098+ /* * Which GraphIndexes ought to occur in m_main_chunkindex, based on m_entries. */
2099+ std::set<GraphIndex> expected_chunkindex;
20222100 /* * Whether compaction is possible in the current state. */
20232101 bool compact_possible{true };
20242102
@@ -2033,6 +2111,11 @@ void TxGraphImpl::SanityCheck() const
20332111 assert (GetRefGraph (*entry.m_ref ) == this );
20342112 assert (GetRefIndex (*entry.m_ref ) == idx);
20352113 }
2114+ if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end ()) {
2115+ // Remember which entries we see a chunkindex entry for.
2116+ assert (entry.m_locator [0 ].IsPresent ());
2117+ expected_chunkindex.insert (idx);
2118+ }
20362119 // Verify the Entry m_locators.
20372120 bool was_present{false }, was_removed{false };
20382121 for (int level = 0 ; level < MAX_LEVELS; ++level) {
@@ -2145,6 +2228,20 @@ void TxGraphImpl::SanityCheck() const
21452228 if (compact_possible) {
21462229 assert (actual_unlinked.empty ());
21472230 }
2231+
2232+ // Finally, check the chunk index.
2233+ std::set<GraphIndex> actual_chunkindex;
2234+ FeeFrac last_chunk_feerate;
2235+ for (const auto & chunk : m_main_chunkindex) {
2236+ GraphIndex idx = chunk.m_graph_index ;
2237+ actual_chunkindex.insert (idx);
2238+ auto chunk_feerate = m_entries[idx].m_main_chunk_feerate ;
2239+ if (!last_chunk_feerate.IsEmpty ()) {
2240+ assert (FeeRateCompare (last_chunk_feerate, chunk_feerate) >= 0 );
2241+ }
2242+ last_chunk_feerate = chunk_feerate;
2243+ }
2244+ assert (actual_chunkindex == expected_chunkindex);
21482245}
21492246
21502247void TxGraphImpl::DoWork () noexcept
0 commit comments