Skip to content

Commit 9095d8a

Browse files
committed
txgraph: Maintain chunk index (preparation)
This is preparation for exposing mining and eviction functionality in TxGraph.
1 parent 87e74e1 commit 9095d8a

File tree

1 file changed

+136
-39
lines changed

1 file changed

+136
-39
lines changed

src/txgraph.cpp

Lines changed: 136 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
360395
public:
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

549589
void 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

21502247
void TxGraphImpl::DoWork() noexcept

0 commit comments

Comments
 (0)