diff --git a/src/test/fuzz/txgraph.cpp b/src/test/fuzz/txgraph.cpp index 09fc85bfe2f..2c9fda9cd93 100644 --- a/src/test/fuzz/txgraph.cpp +++ b/src/test/fuzz/txgraph.cpp @@ -748,6 +748,32 @@ FUZZ_TARGET(txgraph) } builder_data.done = new_done; break; + } else if (!main_sim.IsOversized() && command-- == 0) { + // GetWorstMainChunk. + auto [worst_chunk, worst_chunk_feerate] = real->GetWorstMainChunk(); + // Just do some sanity checks here. Consistency with GetBlockBuilder is checked + // below. + if (main_sim.GetTransactionCount() == 0) { + assert(worst_chunk.empty()); + assert(worst_chunk_feerate.IsEmpty()); + } else { + assert(!worst_chunk.empty()); + SimTxGraph::SetType done; + FeePerWeight sum; + for (TxGraph::Ref* ref : worst_chunk) { + // Each transaction in the chunk must exist in the main graph. + auto simpos = main_sim.Find(ref); + assert(simpos != SimTxGraph::MISSING); + sum += main_sim.graph.FeeRate(simpos); + // Make sure the chunk contains no duplicate transactions. + assert(!done[simpos]); + done.Set(simpos); + // All elements are preceded by all their descendants. + assert(main_sim.graph.Descendants(simpos).IsSubsetOf(done)); + } + assert(sum == worst_chunk_feerate); + } + break; } } } @@ -806,6 +832,8 @@ FUZZ_TARGET(txgraph) // if nothing is skipped. auto builder = real->GetBlockBuilder(); std::vector vec_builder; + std::vector last_chunk; + FeePerWeight last_chunk_feerate; while (auto chunk = builder->GetCurrentChunk()) { FeePerWeight sum; for (TxGraph::Ref* ref : chunk->first) { @@ -820,10 +848,18 @@ FUZZ_TARGET(txgraph) vec_builder.push_back(simpos); } assert(sum == chunk->second); + last_chunk = std::move(chunk->first); + last_chunk_feerate = chunk->second; builder->Include(); } assert(vec_builder == vec1); + // The last chunk returned by the BlockBuilder must match GetWorstMainChunk, in reverse. + std::reverse(last_chunk.begin(), last_chunk.end()); + auto [worst_chunk, worst_chunk_feerate] = real->GetWorstMainChunk(); + assert(last_chunk == worst_chunk); + assert(last_chunk_feerate == worst_chunk_feerate); + // Check that the implied ordering gives rise to a combined diagram that matches the // diagram constructed from the individual cluster linearization chunkings. auto main_real_diagram = get_diagram_fn(/*main_only=*/true); diff --git a/src/txgraph.cpp b/src/txgraph.cpp index e88af40c25c..6d04ba808b5 100644 --- a/src/txgraph.cpp +++ b/src/txgraph.cpp @@ -545,6 +545,7 @@ public: std::pair, std::vector> GetMainStagingDiagrams() noexcept final; std::unique_ptr GetBlockBuilder() noexcept final; + std::pair, FeePerWeight> GetWorstMainChunk() noexcept final; void SanityCheck() const final; }; @@ -2379,6 +2380,26 @@ std::unique_ptr TxGraphImpl::GetBlockBuilder() noexcept return std::make_unique(*this); } +std::pair, FeePerWeight> TxGraphImpl::GetWorstMainChunk() noexcept +{ + std::pair, FeePerWeight> ret; + // Make sure all clusters in main are up to date, and acceptable. + MakeAllAcceptable(0); + Assume(m_main_clusterset.m_deps_to_add.empty()); + // If the graph is not empty, populate ret. + if (!m_main_chunkindex.empty()) { + const auto& chunk_data = *m_main_chunkindex.rbegin(); + const auto& chunk_end_entry = m_entries[chunk_data.m_graph_index]; + Cluster* cluster = chunk_end_entry.m_locator[0].cluster; + ret.first.resize(chunk_data.m_chunk_count); + auto start_pos = chunk_end_entry.m_main_lin_index + 1 - chunk_data.m_chunk_count; + cluster->GetClusterRefs(*this, ret.first, start_pos); + std::reverse(ret.first.begin(), ret.first.end()); + ret.second = chunk_end_entry.m_main_chunk_feerate; + } + return ret; +} + } // namespace TxGraph::Ref::~Ref() diff --git a/src/txgraph.h b/src/txgraph.h index 0aeff5af281..9cfbe70acdc 100644 --- a/src/txgraph.h +++ b/src/txgraph.h @@ -192,6 +192,11 @@ public: * oversized. While the returned object exists, no mutators on the main graph are allowed. * The BlockBuilder object must not outlive the TxGraph it was created with. */ virtual std::unique_ptr GetBlockBuilder() noexcept = 0; + /** Get the last chunk in the main graph, i.e., the last chunk that would be returned by a + * BlockBuilder created now, together with its feerate. The chunk is returned in + * reverse-topological order, so every element is preceded by all its descendants. The main + * graph must not be oversized. If the graph is empty, {{}, FeePerWeight{}} is returned. */ + virtual std::pair, FeePerWeight> GetWorstMainChunk() noexcept = 0; /** Perform an internal consistency check on this object. */ virtual void SanityCheck() const = 0;