mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 23:09:44 -04:00
Merge a0becaaa9c
into c5e44a0435
This commit is contained in:
commit
c1d55574da
3 changed files with 709 additions and 37 deletions
|
@ -11,6 +11,7 @@
|
||||||
#include <util/feefrac.h>
|
#include <util/feefrac.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
@ -52,6 +53,9 @@ struct SimTxGraph
|
||||||
std::optional<bool> oversized;
|
std::optional<bool> oversized;
|
||||||
/** The configured maximum number of transactions per cluster. */
|
/** The configured maximum number of transactions per cluster. */
|
||||||
DepGraphIndex max_cluster_count;
|
DepGraphIndex max_cluster_count;
|
||||||
|
/** Which transactions have been modified in the graph since creation, either directly or by
|
||||||
|
* being in a cluster which includes modifications. Only relevant for the staging graph. */
|
||||||
|
SetType modified;
|
||||||
|
|
||||||
/** Construct a new SimTxGraph with the specified maximum cluster count. */
|
/** Construct a new SimTxGraph with the specified maximum cluster count. */
|
||||||
explicit SimTxGraph(DepGraphIndex max_cluster) : max_cluster_count(max_cluster) {}
|
explicit SimTxGraph(DepGraphIndex max_cluster) : max_cluster_count(max_cluster) {}
|
||||||
|
@ -80,9 +84,24 @@ struct SimTxGraph
|
||||||
return *oversized;
|
return *oversized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MakeModified(DepGraphIndex index)
|
||||||
|
{
|
||||||
|
modified |= graph.GetConnectedComponent(graph.Positions(), index);
|
||||||
|
}
|
||||||
|
|
||||||
/** Determine the number of (non-removed) transactions in the graph. */
|
/** Determine the number of (non-removed) transactions in the graph. */
|
||||||
DepGraphIndex GetTransactionCount() const { return graph.TxCount(); }
|
DepGraphIndex GetTransactionCount() const { return graph.TxCount(); }
|
||||||
|
|
||||||
|
/** Get the sum of all fees/sizes in the graph. */
|
||||||
|
FeePerWeight SumAll() const
|
||||||
|
{
|
||||||
|
FeePerWeight ret;
|
||||||
|
for (auto i : graph.Positions()) {
|
||||||
|
ret += graph.FeeRate(i);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/** Get the position where ref occurs in this simulated graph, or -1 if it does not. */
|
/** Get the position where ref occurs in this simulated graph, or -1 if it does not. */
|
||||||
Pos Find(const TxGraph::Ref* ref) const
|
Pos Find(const TxGraph::Ref* ref) const
|
||||||
{
|
{
|
||||||
|
@ -104,6 +123,7 @@ struct SimTxGraph
|
||||||
{
|
{
|
||||||
assert(graph.TxCount() < MAX_TRANSACTIONS);
|
assert(graph.TxCount() < MAX_TRANSACTIONS);
|
||||||
auto simpos = graph.AddTransaction(feerate);
|
auto simpos = graph.AddTransaction(feerate);
|
||||||
|
MakeModified(simpos);
|
||||||
assert(graph.Positions()[simpos]);
|
assert(graph.Positions()[simpos]);
|
||||||
simmap[simpos] = std::make_shared<TxGraph::Ref>();
|
simmap[simpos] = std::make_shared<TxGraph::Ref>();
|
||||||
auto ptr = simmap[simpos].get();
|
auto ptr = simmap[simpos].get();
|
||||||
|
@ -119,6 +139,7 @@ struct SimTxGraph
|
||||||
auto chl_pos = Find(child);
|
auto chl_pos = Find(child);
|
||||||
if (chl_pos == MISSING) return;
|
if (chl_pos == MISSING) return;
|
||||||
graph.AddDependencies(SetType::Singleton(par_pos), chl_pos);
|
graph.AddDependencies(SetType::Singleton(par_pos), chl_pos);
|
||||||
|
MakeModified(par_pos);
|
||||||
// This may invalidate our cached oversized value.
|
// This may invalidate our cached oversized value.
|
||||||
if (oversized.has_value() && !*oversized) oversized = std::nullopt;
|
if (oversized.has_value() && !*oversized) oversized = std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -128,6 +149,7 @@ struct SimTxGraph
|
||||||
{
|
{
|
||||||
auto pos = Find(ref);
|
auto pos = Find(ref);
|
||||||
if (pos == MISSING) return;
|
if (pos == MISSING) return;
|
||||||
|
// No need to invoke MakeModified, because this equally affects main and staging.
|
||||||
graph.FeeRate(pos).fee = fee;
|
graph.FeeRate(pos).fee = fee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +158,7 @@ struct SimTxGraph
|
||||||
{
|
{
|
||||||
auto pos = Find(ref);
|
auto pos = Find(ref);
|
||||||
if (pos == MISSING) return;
|
if (pos == MISSING) return;
|
||||||
|
MakeModified(pos);
|
||||||
graph.RemoveTransactions(SetType::Singleton(pos));
|
graph.RemoveTransactions(SetType::Singleton(pos));
|
||||||
simrevmap.erase(simmap[pos].get());
|
simrevmap.erase(simmap[pos].get());
|
||||||
// Retain the TxGraph::Ref corresponding to this position, so the Ref destruction isn't
|
// Retain the TxGraph::Ref corresponding to this position, so the Ref destruction isn't
|
||||||
|
@ -160,6 +183,7 @@ struct SimTxGraph
|
||||||
auto remove = std::partition(removed.begin(), removed.end(), [&](auto& arg) { return arg.get() != ref; });
|
auto remove = std::partition(removed.begin(), removed.end(), [&](auto& arg) { return arg.get() != ref; });
|
||||||
removed.erase(remove, removed.end());
|
removed.erase(remove, removed.end());
|
||||||
} else {
|
} else {
|
||||||
|
MakeModified(pos);
|
||||||
graph.RemoveTransactions(SetType::Singleton(pos));
|
graph.RemoveTransactions(SetType::Singleton(pos));
|
||||||
simrevmap.erase(simmap[pos].get());
|
simrevmap.erase(simmap[pos].get());
|
||||||
simmap[pos].reset();
|
simmap[pos].reset();
|
||||||
|
@ -245,6 +269,24 @@ FUZZ_TARGET(txgraph)
|
||||||
sims.reserve(2);
|
sims.reserve(2);
|
||||||
sims.emplace_back(max_count);
|
sims.emplace_back(max_count);
|
||||||
|
|
||||||
|
/** Struct encapsulating information about a BlockBuilder that's currently live. */
|
||||||
|
struct BlockBuilderData
|
||||||
|
{
|
||||||
|
/** BlockBuilder object from real. */
|
||||||
|
std::unique_ptr<TxGraph::BlockBuilder> builder;
|
||||||
|
/** The set of transactions marked as included in *builder. */
|
||||||
|
SimTxGraph::SetType included;
|
||||||
|
/** The set of transactions marked as included or skipped in *builder. */
|
||||||
|
SimTxGraph::SetType done;
|
||||||
|
/** The last chunk feerate returned by *builder. IsEmpty() if none yet. */
|
||||||
|
FeePerWeight last_feerate;
|
||||||
|
|
||||||
|
BlockBuilderData(std::unique_ptr<TxGraph::BlockBuilder> builder_in) : builder(std::move(builder_in)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Currently active block builders. */
|
||||||
|
std::vector<BlockBuilderData> block_builders;
|
||||||
|
|
||||||
/** Function to pick any Ref (for either sim in sims: from sim.simmap or sim.removed, or the
|
/** Function to pick any Ref (for either sim in sims: from sim.simmap or sim.removed, or the
|
||||||
* empty Ref). */
|
* empty Ref). */
|
||||||
auto pick_fn = [&]() noexcept -> TxGraph::Ref* {
|
auto pick_fn = [&]() noexcept -> TxGraph::Ref* {
|
||||||
|
@ -282,9 +324,44 @@ FUZZ_TARGET(txgraph)
|
||||||
return &empty_ref;
|
return &empty_ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Function to construct the correct fee-size diagram a real graph has based on its graph
|
||||||
|
* order (as reported by GetCluster(), so it works for both main and staging). */
|
||||||
|
auto get_diagram_fn = [&](bool main_only) -> std::vector<FeeFrac> {
|
||||||
|
int level = main_only ? 0 : sims.size() - 1;
|
||||||
|
auto& sim = sims[level];
|
||||||
|
// For every transaction in the graph, request its cluster, and throw them into a set.
|
||||||
|
std::set<std::vector<TxGraph::Ref*>> clusters;
|
||||||
|
for (auto i : sim.graph.Positions()) {
|
||||||
|
auto ref = sim.GetRef(i);
|
||||||
|
clusters.insert(real->GetCluster(*ref, main_only));
|
||||||
|
}
|
||||||
|
// Compute the chunkings of each (deduplicated) cluster.
|
||||||
|
size_t num_tx{0};
|
||||||
|
std::vector<FeeFrac> chunk_feerates;
|
||||||
|
for (const auto& cluster : clusters) {
|
||||||
|
num_tx += cluster.size();
|
||||||
|
std::vector<SimTxGraph::Pos> linearization;
|
||||||
|
linearization.reserve(cluster.size());
|
||||||
|
for (auto refptr : cluster) linearization.push_back(sim.Find(refptr));
|
||||||
|
for (const FeeFrac& chunk_feerate : ChunkLinearization(sim.graph, linearization)) {
|
||||||
|
chunk_feerates.push_back(chunk_feerate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Verify the number of transactions after deduplicating clusters. This implicitly verifies
|
||||||
|
// that GetCluster on each element of a cluster reports the cluster transactions in the same
|
||||||
|
// order.
|
||||||
|
assert(num_tx == sim.GetTransactionCount());
|
||||||
|
// Sort by feerate only, since violating topological constraints within same-feerate
|
||||||
|
// chunks won't affect diagram comparisons.
|
||||||
|
std::sort(chunk_feerates.begin(), chunk_feerates.end(), std::greater{});
|
||||||
|
return chunk_feerates;
|
||||||
|
};
|
||||||
|
|
||||||
LIMITED_WHILE(provider.remaining_bytes() > 0, 200) {
|
LIMITED_WHILE(provider.remaining_bytes() > 0, 200) {
|
||||||
// Read a one-byte command.
|
// Read a one-byte command.
|
||||||
int command = provider.ConsumeIntegral<uint8_t>();
|
int command = provider.ConsumeIntegral<uint8_t>();
|
||||||
|
int orig_command = command;
|
||||||
|
|
||||||
// Treat the lowest bit of a command as a flag (which selects a variant of some of the
|
// Treat the lowest bit of a command as a flag (which selects a variant of some of the
|
||||||
// operations), and the second-lowest bit as a way of selecting main vs. staging, and leave
|
// operations), and the second-lowest bit as a way of selecting main vs. staging, and leave
|
||||||
// the rest of the bits in command.
|
// the rest of the bits in command.
|
||||||
|
@ -292,6 +369,11 @@ FUZZ_TARGET(txgraph)
|
||||||
bool use_main = command & 2;
|
bool use_main = command & 2;
|
||||||
command >>= 2;
|
command >>= 2;
|
||||||
|
|
||||||
|
/** Use the bottom 2 bits of command to select an entry in the block_builders vector (if
|
||||||
|
* any). These use the same bits as alt/use_main, so don't use those in actions below
|
||||||
|
* where builder_idx is used as well. */
|
||||||
|
int builder_idx = block_builders.empty() ? -1 : int((orig_command & 3) % block_builders.size());
|
||||||
|
|
||||||
// Provide convenient aliases for the top simulated graph (main, or staging if it exists),
|
// Provide convenient aliases for the top simulated graph (main, or staging if it exists),
|
||||||
// one for the simulated graph selected based on use_main (for operations that can operate
|
// one for the simulated graph selected based on use_main (for operations that can operate
|
||||||
// on both graphs), and one that always refers to the main graph.
|
// on both graphs), and one that always refers to the main graph.
|
||||||
|
@ -302,7 +384,7 @@ FUZZ_TARGET(txgraph)
|
||||||
// Keep decrementing command for each applicable operation, until one is hit. Multiple
|
// Keep decrementing command for each applicable operation, until one is hit. Multiple
|
||||||
// iterations may be necessary.
|
// iterations may be necessary.
|
||||||
while (true) {
|
while (true) {
|
||||||
if (top_sim.GetTransactionCount() < SimTxGraph::MAX_TRANSACTIONS && command-- == 0) {
|
if ((block_builders.empty() || sims.size() > 1) && top_sim.GetTransactionCount() < SimTxGraph::MAX_TRANSACTIONS && command-- == 0) {
|
||||||
// AddTransaction.
|
// AddTransaction.
|
||||||
int64_t fee;
|
int64_t fee;
|
||||||
int32_t size;
|
int32_t size;
|
||||||
|
@ -324,7 +406,7 @@ FUZZ_TARGET(txgraph)
|
||||||
// Move it in place.
|
// Move it in place.
|
||||||
*ref_loc = std::move(ref);
|
*ref_loc = std::move(ref);
|
||||||
break;
|
break;
|
||||||
} else if (top_sim.GetTransactionCount() + top_sim.removed.size() > 1 && command-- == 0) {
|
} else if ((block_builders.empty() || sims.size() > 1) && top_sim.GetTransactionCount() + top_sim.removed.size() > 1 && command-- == 0) {
|
||||||
// AddDependency.
|
// AddDependency.
|
||||||
auto par = pick_fn();
|
auto par = pick_fn();
|
||||||
auto chl = pick_fn();
|
auto chl = pick_fn();
|
||||||
|
@ -338,7 +420,7 @@ FUZZ_TARGET(txgraph)
|
||||||
top_sim.AddDependency(par, chl);
|
top_sim.AddDependency(par, chl);
|
||||||
real->AddDependency(*par, *chl);
|
real->AddDependency(*par, *chl);
|
||||||
break;
|
break;
|
||||||
} else if (top_sim.removed.size() < 100 && command-- == 0) {
|
} else if ((block_builders.empty() || sims.size() > 1) && top_sim.removed.size() < 100 && command-- == 0) {
|
||||||
// RemoveTransaction. Either all its ancestors or all its descendants are also
|
// RemoveTransaction. Either all its ancestors or all its descendants are also
|
||||||
// removed (if any), to make sure TxGraph's reordering of removals and dependencies
|
// removed (if any), to make sure TxGraph's reordering of removals and dependencies
|
||||||
// has no effect.
|
// has no effect.
|
||||||
|
@ -368,7 +450,7 @@ FUZZ_TARGET(txgraph)
|
||||||
}
|
}
|
||||||
sel_sim.removed.pop_back();
|
sel_sim.removed.pop_back();
|
||||||
break;
|
break;
|
||||||
} else if (command-- == 0) {
|
} else if (block_builders.empty() && command-- == 0) {
|
||||||
// ~Ref (of any transaction).
|
// ~Ref (of any transaction).
|
||||||
std::vector<TxGraph::Ref*> to_destroy;
|
std::vector<TxGraph::Ref*> to_destroy;
|
||||||
to_destroy.push_back(pick_fn());
|
to_destroy.push_back(pick_fn());
|
||||||
|
@ -390,7 +472,7 @@ FUZZ_TARGET(txgraph)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else if (command-- == 0) {
|
} else if (block_builders.empty() && command-- == 0) {
|
||||||
// SetTransactionFee.
|
// SetTransactionFee.
|
||||||
int64_t fee;
|
int64_t fee;
|
||||||
if (alt) {
|
if (alt) {
|
||||||
|
@ -444,6 +526,7 @@ FUZZ_TARGET(txgraph)
|
||||||
// Just do some quick checks that the reported value is in range. A full
|
// Just do some quick checks that the reported value is in range. A full
|
||||||
// recomputation of expected chunk feerates is done at the end.
|
// recomputation of expected chunk feerates is done at the end.
|
||||||
assert(feerate.size >= main_sim.graph.FeeRate(simpos).size);
|
assert(feerate.size >= main_sim.graph.FeeRate(simpos).size);
|
||||||
|
assert(feerate.size <= main_sim.SumAll().size);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
} else if (!sel_sim.IsOversized() && command-- == 0) {
|
} else if (!sel_sim.IsOversized() && command-- == 0) {
|
||||||
|
@ -517,9 +600,10 @@ FUZZ_TARGET(txgraph)
|
||||||
} else if (sims.size() < 2 && command-- == 0) {
|
} else if (sims.size() < 2 && command-- == 0) {
|
||||||
// StartStaging.
|
// StartStaging.
|
||||||
sims.emplace_back(sims.back());
|
sims.emplace_back(sims.back());
|
||||||
|
sims.back().modified = SimTxGraph::SetType{};
|
||||||
real->StartStaging();
|
real->StartStaging();
|
||||||
break;
|
break;
|
||||||
} else if (sims.size() > 1 && command-- == 0) {
|
} else if (block_builders.empty() && sims.size() > 1 && command-- == 0) {
|
||||||
// CommitStaging.
|
// CommitStaging.
|
||||||
real->CommitStaging();
|
real->CommitStaging();
|
||||||
sims.erase(sims.begin());
|
sims.erase(sims.begin());
|
||||||
|
@ -586,6 +670,110 @@ FUZZ_TARGET(txgraph)
|
||||||
// DoWork.
|
// DoWork.
|
||||||
real->DoWork();
|
real->DoWork();
|
||||||
break;
|
break;
|
||||||
|
} else if (sims.size() == 2 && !sims[0].IsOversized() && !sims[1].IsOversized() && command-- == 0) {
|
||||||
|
// GetMainStagingDiagrams()
|
||||||
|
auto [main_diagram, staged_diagram] = real->GetMainStagingDiagrams();
|
||||||
|
auto sum_main = std::accumulate(main_diagram.begin(), main_diagram.end(), FeeFrac{});
|
||||||
|
auto sum_staged = std::accumulate(staged_diagram.begin(), staged_diagram.end(), FeeFrac{});
|
||||||
|
auto diagram_gain = sum_staged - sum_main;
|
||||||
|
auto real_gain = sims[1].SumAll() - sims[0].SumAll();
|
||||||
|
// Just check that the total fee gained/lost and size gained/lost according to the
|
||||||
|
// diagram matches the difference in these values in the simulated graph. A more
|
||||||
|
// complete check of the GetMainStagingDiagrams result is performed at the end.
|
||||||
|
assert(diagram_gain == real_gain);
|
||||||
|
// Check that the feerates in each diagram are monotonically decreasing.
|
||||||
|
for (size_t i = 1; i < main_diagram.size(); ++i) {
|
||||||
|
assert(FeeRateCompare(main_diagram[i], main_diagram[i - 1]) <= 0);
|
||||||
|
}
|
||||||
|
for (size_t i = 1; i < staged_diagram.size(); ++i) {
|
||||||
|
assert(FeeRateCompare(staged_diagram[i], staged_diagram[i - 1]) <= 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if (block_builders.size() < 4 && !main_sim.IsOversized() && command-- == 0) {
|
||||||
|
// GetBlockBuilder.
|
||||||
|
block_builders.emplace_back(real->GetBlockBuilder());
|
||||||
|
break;
|
||||||
|
} else if (!block_builders.empty() && command-- == 0) {
|
||||||
|
// ~BlockBuilder.
|
||||||
|
block_builders.erase(block_builders.begin() + builder_idx);
|
||||||
|
break;
|
||||||
|
} else if (!block_builders.empty() && command-- == 0) {
|
||||||
|
// BlockBuilder::GetCurrentChunk, followed by Include/Skip.
|
||||||
|
auto& builder_data = block_builders[builder_idx];
|
||||||
|
auto new_included = builder_data.included;
|
||||||
|
auto new_done = builder_data.done;
|
||||||
|
auto chunk = builder_data.builder->GetCurrentChunk();
|
||||||
|
if (chunk) {
|
||||||
|
// Chunk feerates must be monotonously decreasing.
|
||||||
|
if (!builder_data.last_feerate.IsEmpty()) {
|
||||||
|
assert(!(chunk->second >> builder_data.last_feerate));
|
||||||
|
}
|
||||||
|
builder_data.last_feerate = chunk->second;
|
||||||
|
// Verify the contents of GetCurrentChunk.
|
||||||
|
FeePerWeight sum_feerate;
|
||||||
|
for (TxGraph::Ref* ref : chunk->first) {
|
||||||
|
// Each transaction in the chunk must exist in the main graph.
|
||||||
|
auto simpos = main_sim.Find(ref);
|
||||||
|
assert(simpos != SimTxGraph::MISSING);
|
||||||
|
// Verify the claimed chunk feerate.
|
||||||
|
sum_feerate += main_sim.graph.FeeRate(simpos);
|
||||||
|
// Make sure no transaction is reported twice.
|
||||||
|
assert(!new_done[simpos]);
|
||||||
|
new_done.Set(simpos);
|
||||||
|
// The concatenation of all included transactions must be topologically valid.
|
||||||
|
new_included.Set(simpos);
|
||||||
|
assert(main_sim.graph.Ancestors(simpos).IsSubsetOf(new_included));
|
||||||
|
}
|
||||||
|
assert(sum_feerate == chunk->second);
|
||||||
|
} else {
|
||||||
|
// When we reach the end, if nothing was skipped, the entire graph should have
|
||||||
|
// been reported.
|
||||||
|
if (builder_data.done == builder_data.included) {
|
||||||
|
assert(builder_data.done.Count() == main_sim.GetTransactionCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Possibly invoke GetCurrentChunk() again, which should give the same result.
|
||||||
|
if ((orig_command % 7) >= 5) {
|
||||||
|
auto chunk2 = builder_data.builder->GetCurrentChunk();
|
||||||
|
assert(chunk == chunk2);
|
||||||
|
}
|
||||||
|
// Skip or include.
|
||||||
|
if ((orig_command % 5) >= 3) {
|
||||||
|
// Skip.
|
||||||
|
builder_data.builder->Skip();
|
||||||
|
} else {
|
||||||
|
// Include.
|
||||||
|
builder_data.builder->Include();
|
||||||
|
builder_data.included = new_included;
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -639,6 +827,94 @@ FUZZ_TARGET(txgraph)
|
||||||
assert(FeeRateCompare(after_feerate, pos_feerate) <= 0);
|
assert(FeeRateCompare(after_feerate, pos_feerate) <= 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The same order should be obtained through a BlockBuilder as implied by CompareMainOrder,
|
||||||
|
// if nothing is skipped.
|
||||||
|
auto builder = real->GetBlockBuilder();
|
||||||
|
std::vector<SimTxGraph::Pos> vec_builder;
|
||||||
|
std::vector<TxGraph::Ref*> last_chunk;
|
||||||
|
FeePerWeight last_chunk_feerate;
|
||||||
|
while (auto chunk = builder->GetCurrentChunk()) {
|
||||||
|
FeePerWeight sum;
|
||||||
|
for (TxGraph::Ref* ref : chunk->first) {
|
||||||
|
// The reported chunk feerate must match the chunk feerate obtained by asking
|
||||||
|
// it for each of the chunk's transactions individually.
|
||||||
|
assert(real->GetMainChunkFeerate(*ref) == chunk->second);
|
||||||
|
// Verify the chunk feerate matches the sum of the reported individual feerates.
|
||||||
|
sum += real->GetIndividualFeerate(*ref);
|
||||||
|
// Chunks must contain transactions that exist in the graph.
|
||||||
|
auto simpos = sims[0].Find(ref);
|
||||||
|
assert(simpos != SimTxGraph::MISSING);
|
||||||
|
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);
|
||||||
|
auto main_implied_diagram = ChunkLinearization(sims[0].graph, vec1);
|
||||||
|
assert(CompareChunks(main_real_diagram, main_implied_diagram) == 0);
|
||||||
|
|
||||||
|
if (sims.size() >= 2 && !sims[1].IsOversized()) {
|
||||||
|
// When the staging graph is not oversized as well, call GetMainStagingDiagrams, and
|
||||||
|
// fully verify the result.
|
||||||
|
auto [main_cmp_diagram, stage_cmp_diagram] = real->GetMainStagingDiagrams();
|
||||||
|
// Check that the feerates in each diagram are monotonically decreasing.
|
||||||
|
for (size_t i = 1; i < main_cmp_diagram.size(); ++i) {
|
||||||
|
assert(FeeRateCompare(main_cmp_diagram[i], main_cmp_diagram[i - 1]) <= 0);
|
||||||
|
}
|
||||||
|
for (size_t i = 1; i < stage_cmp_diagram.size(); ++i) {
|
||||||
|
assert(FeeRateCompare(stage_cmp_diagram[i], stage_cmp_diagram[i - 1]) <= 0);
|
||||||
|
}
|
||||||
|
// Treat the diagrams as sets of chunk feerates, and sort them in the same way so that
|
||||||
|
// std::set_difference can be used on them below. The exact ordering does not matter
|
||||||
|
// here, but it has to be consistent with the one used in main_diagram and
|
||||||
|
// stage_diagram).
|
||||||
|
std::sort(main_cmp_diagram.begin(), main_cmp_diagram.end(), std::greater{});
|
||||||
|
std::sort(stage_cmp_diagram.begin(), stage_cmp_diagram.end(), std::greater{});
|
||||||
|
// Find the chunks that appear in main_diagram but are missing from main_cmp_diagram.
|
||||||
|
// This is allowed, because GetMainStagingDiagrams omits clusters in main unaffected
|
||||||
|
// by staging.
|
||||||
|
std::vector<FeeFrac> missing_main_cmp;
|
||||||
|
std::set_difference(main_real_diagram.begin(), main_real_diagram.end(),
|
||||||
|
main_cmp_diagram.begin(), main_cmp_diagram.end(),
|
||||||
|
std::inserter(missing_main_cmp, missing_main_cmp.end()),
|
||||||
|
std::greater{});
|
||||||
|
assert(main_cmp_diagram.size() + missing_main_cmp.size() == main_real_diagram.size());
|
||||||
|
// Do the same for chunks in stage_diagram missing from stage_cmp_diagram.
|
||||||
|
auto stage_real_diagram = get_diagram_fn(false);
|
||||||
|
std::vector<FeeFrac> missing_stage_cmp;
|
||||||
|
std::set_difference(stage_real_diagram.begin(), stage_real_diagram.end(),
|
||||||
|
stage_cmp_diagram.begin(), stage_cmp_diagram.end(),
|
||||||
|
std::inserter(missing_stage_cmp, missing_stage_cmp.end()),
|
||||||
|
std::greater{});
|
||||||
|
assert(stage_cmp_diagram.size() + missing_stage_cmp.size() == stage_real_diagram.size());
|
||||||
|
// The missing chunks must be equal across main & staging (otherwise they couldn't have
|
||||||
|
// been omitted).
|
||||||
|
assert(missing_main_cmp == missing_stage_cmp);
|
||||||
|
|
||||||
|
// The missing part must include at least all transactions in staging which have not been
|
||||||
|
// modified, or been in a cluster together with modified transactions, since they were
|
||||||
|
// copied from main. Note that due to the reordering of removals w.r.t. dependency
|
||||||
|
// additions, it is possible that the real implementation found more unaffected things.
|
||||||
|
FeeFrac missing_real;
|
||||||
|
for (const auto& feerate : missing_main_cmp) missing_real += feerate;
|
||||||
|
FeeFrac missing_expected = sims[1].graph.FeeRate(sims[1].graph.Positions() - sims[1].modified);
|
||||||
|
// Note that missing_real.fee < missing_expected.fee is possible to due the presence of
|
||||||
|
// negative-fee transactions.
|
||||||
|
assert(missing_real.size >= missing_expected.size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(real->HaveStaging() == (sims.size() > 1));
|
assert(real->HaveStaging() == (sims.size() > 1));
|
||||||
|
@ -714,6 +990,8 @@ FUZZ_TARGET(txgraph)
|
||||||
// Sanity check again (because invoking inspectors may modify internal unobservable state).
|
// Sanity check again (because invoking inspectors may modify internal unobservable state).
|
||||||
real->SanityCheck();
|
real->SanityCheck();
|
||||||
|
|
||||||
|
// Kill the block builders.
|
||||||
|
block_builders.clear();
|
||||||
// Kill the TxGraph object.
|
// Kill the TxGraph object.
|
||||||
real.reset();
|
real.reset();
|
||||||
// Kill the simulated graphs, with all remaining Refs in it. If any, this verifies that Refs
|
// Kill the simulated graphs, with all remaining Refs in it. If any, this verifies that Refs
|
||||||
|
|
417
src/txgraph.cpp
417
src/txgraph.cpp
|
@ -9,6 +9,7 @@
|
||||||
#include <util/bitset.h>
|
#include <util/bitset.h>
|
||||||
#include <util/check.h>
|
#include <util/check.h>
|
||||||
#include <util/feefrac.h>
|
#include <util/feefrac.h>
|
||||||
|
#include <util/vector.h>
|
||||||
|
|
||||||
#include <compare>
|
#include <compare>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -140,6 +141,8 @@ public:
|
||||||
void ApplyDependencies(TxGraphImpl& graph, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept;
|
void ApplyDependencies(TxGraphImpl& graph, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept;
|
||||||
/** Improve the linearization of this Cluster. */
|
/** Improve the linearization of this Cluster. */
|
||||||
void Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept;
|
void Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept;
|
||||||
|
/** For every chunk in the cluster, append its FeeFrac to ret. */
|
||||||
|
void AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept;
|
||||||
|
|
||||||
// Functions that implement the Cluster-specific side of public TxGraph functions.
|
// Functions that implement the Cluster-specific side of public TxGraph functions.
|
||||||
|
|
||||||
|
@ -149,8 +152,10 @@ public:
|
||||||
/** Process elements from the front of args that apply to this cluster, and append Refs for the
|
/** Process elements from the front of args that apply to this cluster, and append Refs for the
|
||||||
* union of their descendants to output. */
|
* union of their descendants to output. */
|
||||||
void GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept;
|
void GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept;
|
||||||
/** Get a vector of Refs for all elements of this Cluster, in linearization order. */
|
/** Populate range with refs for the transactions in this Cluster's linearization, from
|
||||||
std::vector<TxGraph::Ref*> GetClusterRefs(const TxGraphImpl& graph) noexcept;
|
* position start_pos until start_pos+range.size()-1, inclusive. Returns whether that
|
||||||
|
* range includes the last transaction in the linearization. */
|
||||||
|
bool GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept;
|
||||||
/** Get the individual transaction feerate of a Cluster element. */
|
/** Get the individual transaction feerate of a Cluster element. */
|
||||||
FeePerWeight GetIndividualFeerate(DepGraphIndex idx) noexcept;
|
FeePerWeight GetIndividualFeerate(DepGraphIndex idx) noexcept;
|
||||||
/** Modify the fee of a Cluster element. */
|
/** Modify the fee of a Cluster element. */
|
||||||
|
@ -188,6 +193,7 @@ public:
|
||||||
class TxGraphImpl final : public TxGraph
|
class TxGraphImpl final : public TxGraph
|
||||||
{
|
{
|
||||||
friend class Cluster;
|
friend class Cluster;
|
||||||
|
friend class BlockBuilderImpl;
|
||||||
private:
|
private:
|
||||||
/** Internal RNG. */
|
/** Internal RNG. */
|
||||||
FastRandomContext m_rng;
|
FastRandomContext m_rng;
|
||||||
|
@ -252,6 +258,65 @@ private:
|
||||||
/** Next sequence number to assign to created Clusters. */
|
/** Next sequence number to assign to created Clusters. */
|
||||||
uint64_t m_next_sequence_counter{0};
|
uint64_t m_next_sequence_counter{0};
|
||||||
|
|
||||||
|
/** Information about a chunk in the main graph. */
|
||||||
|
struct ChunkData
|
||||||
|
{
|
||||||
|
/** The Entry which is the last transaction of the chunk. */
|
||||||
|
mutable GraphIndex m_graph_index;
|
||||||
|
/** How many transactions the chunk contains (-1 = singleton tail of cluster). */
|
||||||
|
LinearizationIndex m_chunk_count;
|
||||||
|
|
||||||
|
ChunkData(GraphIndex graph_index, LinearizationIndex chunk_count) noexcept :
|
||||||
|
m_graph_index{graph_index}, m_chunk_count{chunk_count} {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Compare two Cluster* by their m_sequence value (while supporting nullptr). */
|
||||||
|
static std::strong_ordering CompareClusters(Cluster* a, Cluster* b) noexcept
|
||||||
|
{
|
||||||
|
// The nullptr pointer compares before everything else.
|
||||||
|
if (a == nullptr || b == nullptr) {
|
||||||
|
return (a != nullptr) <=> (b != nullptr);
|
||||||
|
}
|
||||||
|
// If neither pointer is nullptr, compare the Clusters' sequence numbers.
|
||||||
|
Assume(a == b || a->m_sequence != b->m_sequence);
|
||||||
|
return a->m_sequence <=> b->m_sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Comparator for ChunkData objects in mining order. */
|
||||||
|
class ChunkOrder
|
||||||
|
{
|
||||||
|
const TxGraphImpl* const m_graph;
|
||||||
|
public:
|
||||||
|
explicit ChunkOrder(const TxGraphImpl* graph) : m_graph(graph) {}
|
||||||
|
|
||||||
|
bool operator()(const ChunkData& a, const ChunkData& b) const noexcept
|
||||||
|
{
|
||||||
|
const auto& a_entry = m_graph->m_entries[a.m_graph_index];
|
||||||
|
const auto& b_entry = m_graph->m_entries[b.m_graph_index];
|
||||||
|
// First sort from high feerate to low feerate.
|
||||||
|
auto cmp_feerate = FeeRateCompare(a_entry.m_main_chunk_feerate, b_entry.m_main_chunk_feerate);
|
||||||
|
if (cmp_feerate != 0) return cmp_feerate > 0;
|
||||||
|
// Then sort by increasing Cluster::m_sequence.
|
||||||
|
Assume(a_entry.m_locator[0].IsPresent());
|
||||||
|
Assume(b_entry.m_locator[0].IsPresent());
|
||||||
|
auto cmp_sequence = CompareClusters(a_entry.m_locator[0].cluster, b_entry.m_locator[0].cluster);
|
||||||
|
if (cmp_sequence != 0) return cmp_sequence < 0;
|
||||||
|
// Finally sort by position within the Cluster.
|
||||||
|
return a_entry.m_main_lin_index < b_entry.m_main_lin_index;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Definition for the mining index type. */
|
||||||
|
using ChunkIndex = std::set<ChunkData, ChunkOrder>;
|
||||||
|
|
||||||
|
/** Index of ChunkData objects, indexing the last transaction in each chunk in the main
|
||||||
|
* graph. */
|
||||||
|
ChunkIndex m_main_chunkindex;
|
||||||
|
/** Number of index-observing objects in existence (BlockBuilderImpls). */
|
||||||
|
size_t m_main_chunkindex_observers{0};
|
||||||
|
/** Cache of discarded ChunkIndex node handles to re-use, avoiding additional allocation. */
|
||||||
|
std::vector<ChunkIndex::node_type> m_main_chunkindex_discarded;
|
||||||
|
|
||||||
/** A Locator that describes whether, where, and in which Cluster an Entry appears.
|
/** A Locator that describes whether, where, and in which Cluster an Entry appears.
|
||||||
* Every Entry has MAX_LEVELS locators, as it may appear in one Cluster per level.
|
* Every Entry has MAX_LEVELS locators, as it may appear in one Cluster per level.
|
||||||
*
|
*
|
||||||
|
@ -308,6 +373,9 @@ private:
|
||||||
{
|
{
|
||||||
/** Pointer to the corresponding Ref object if any, or nullptr if unlinked. */
|
/** Pointer to the corresponding Ref object if any, or nullptr if unlinked. */
|
||||||
Ref* m_ref{nullptr};
|
Ref* m_ref{nullptr};
|
||||||
|
/** Iterator to the corresponding ChunkData, if any, and m_main_chunkindex.end() otherwise.
|
||||||
|
* This is initialized on construction of the Entry, in AddTransaction. */
|
||||||
|
ChunkIndex::iterator m_main_chunkindex_iterator;
|
||||||
/** Which Cluster and position therein this Entry appears in. ([0] = main, [1] = staged). */
|
/** Which Cluster and position therein this Entry appears in. ([0] = main, [1] = staged). */
|
||||||
Locator m_locator[MAX_LEVELS];
|
Locator m_locator[MAX_LEVELS];
|
||||||
/** The chunk feerate of this transaction in main (if present in m_locator[0]). */
|
/** The chunk feerate of this transaction in main (if present in m_locator[0]). */
|
||||||
|
@ -322,22 +390,11 @@ private:
|
||||||
/** Set of Entries which have no linked Ref anymore. */
|
/** Set of Entries which have no linked Ref anymore. */
|
||||||
std::vector<GraphIndex> m_unlinked;
|
std::vector<GraphIndex> m_unlinked;
|
||||||
|
|
||||||
/** Compare two Cluster* by their m_sequence value (while supporting nullptr). */
|
|
||||||
static std::strong_ordering CompareClusters(Cluster* a, Cluster* b) noexcept
|
|
||||||
{
|
|
||||||
// The nullptr pointer compares before everything else.
|
|
||||||
if (a == nullptr || b == nullptr) {
|
|
||||||
return (a != nullptr) <=> (b != nullptr);
|
|
||||||
}
|
|
||||||
// If neither pointer is nullptr, compare the Clusters' sequence numbers.
|
|
||||||
Assume(a == b || a->m_sequence != b->m_sequence);
|
|
||||||
return a->m_sequence <=> b->m_sequence;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** Construct a new TxGraphImpl with the specified maximum cluster count. */
|
/** Construct a new TxGraphImpl with the specified maximum cluster count. */
|
||||||
explicit TxGraphImpl(DepGraphIndex max_cluster_count) noexcept :
|
explicit TxGraphImpl(DepGraphIndex max_cluster_count) noexcept :
|
||||||
m_max_cluster_count(max_cluster_count)
|
m_max_cluster_count(max_cluster_count),
|
||||||
|
m_main_chunkindex(ChunkOrder(this))
|
||||||
{
|
{
|
||||||
Assume(max_cluster_count >= 1);
|
Assume(max_cluster_count >= 1);
|
||||||
Assume(max_cluster_count <= MAX_CLUSTER_COUNT_LIMIT);
|
Assume(max_cluster_count <= MAX_CLUSTER_COUNT_LIMIT);
|
||||||
|
@ -378,6 +435,10 @@ public:
|
||||||
void ClearLocator(int level, GraphIndex index) noexcept;
|
void ClearLocator(int level, GraphIndex index) noexcept;
|
||||||
/** Find which Clusters in main conflict with ones in staging. */
|
/** Find which Clusters in main conflict with ones in staging. */
|
||||||
std::vector<Cluster*> GetConflicts() const noexcept;
|
std::vector<Cluster*> GetConflicts() const noexcept;
|
||||||
|
/** Clear an Entry's ChunkData. */
|
||||||
|
void ClearChunkData(Entry& entry) noexcept;
|
||||||
|
/** Give an Entry a ChunkData object. */
|
||||||
|
void CreateChunkData(GraphIndex idx, LinearizationIndex chunk_count) noexcept;
|
||||||
|
|
||||||
// Functions for handling Refs.
|
// Functions for handling Refs.
|
||||||
|
|
||||||
|
@ -394,6 +455,7 @@ public:
|
||||||
{
|
{
|
||||||
auto& entry = m_entries[idx];
|
auto& entry = m_entries[idx];
|
||||||
Assume(entry.m_ref != nullptr);
|
Assume(entry.m_ref != nullptr);
|
||||||
|
Assume(m_main_chunkindex_observers == 0 || !entry.m_locator[0].IsPresent());
|
||||||
entry.m_ref = nullptr;
|
entry.m_ref = nullptr;
|
||||||
// Mark the transaction as to be removed in all levels where it explicitly or implicitly
|
// Mark the transaction as to be removed in all levels where it explicitly or implicitly
|
||||||
// exists.
|
// exists.
|
||||||
|
@ -478,6 +540,10 @@ public:
|
||||||
bool IsOversized(bool main_only = false) noexcept final;
|
bool IsOversized(bool main_only = false) noexcept final;
|
||||||
std::strong_ordering CompareMainOrder(const Ref& a, const Ref& b) noexcept final;
|
std::strong_ordering CompareMainOrder(const Ref& a, const Ref& b) noexcept final;
|
||||||
GraphIndex CountDistinctClusters(std::span<const Ref* const> refs, bool main_only = false) noexcept final;
|
GraphIndex CountDistinctClusters(std::span<const Ref* const> refs, bool main_only = false) noexcept final;
|
||||||
|
std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>> GetMainStagingDiagrams() noexcept final;
|
||||||
|
|
||||||
|
std::unique_ptr<BlockBuilder> GetBlockBuilder() noexcept final;
|
||||||
|
std::pair<std::vector<Ref*>, FeePerWeight> GetWorstMainChunk() noexcept final;
|
||||||
|
|
||||||
void SanityCheck() const final;
|
void SanityCheck() const final;
|
||||||
};
|
};
|
||||||
|
@ -498,6 +564,66 @@ const TxGraphImpl::ClusterSet& TxGraphImpl::GetClusterSet(int level) const noexc
|
||||||
return *m_staging_clusterset;
|
return *m_staging_clusterset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Implementation of the TxGraph::BlockBuilder interface. */
|
||||||
|
class BlockBuilderImpl final : public TxGraph::BlockBuilder
|
||||||
|
{
|
||||||
|
/** Which TxGraphImpl this object is doing block building for. It will have its
|
||||||
|
* m_main_chunkindex_observers incremented as long as this BlockBuilderImpl exists. */
|
||||||
|
TxGraphImpl* const m_graph;
|
||||||
|
/** Clusters which we're not including further transactions from. */
|
||||||
|
std::set<Cluster*> m_excluded_clusters;
|
||||||
|
/** Iterator to the current chunk in the chunk index. end() if nothing further remains. */
|
||||||
|
TxGraphImpl::ChunkIndex::const_iterator m_cur_iter;
|
||||||
|
/** Which cluster the current chunk belongs to, so we can exclude further transactions from it
|
||||||
|
* when that chunk is skipped, or nullptr if we know we're at the end of the current
|
||||||
|
* cluster. */
|
||||||
|
Cluster* m_cur_cluster;
|
||||||
|
|
||||||
|
// Move m_cur_iter / m_cur_cluster to the next acceptable chunk.
|
||||||
|
void Next() noexcept;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Construct a new BlockBuilderImpl to build blocks for the provided graph. */
|
||||||
|
BlockBuilderImpl(TxGraphImpl& graph) noexcept;
|
||||||
|
|
||||||
|
// Implement the public interface.
|
||||||
|
~BlockBuilderImpl() final;
|
||||||
|
std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> GetCurrentChunk() noexcept final;
|
||||||
|
void Include() noexcept final;
|
||||||
|
void Skip() noexcept final;
|
||||||
|
};
|
||||||
|
|
||||||
|
void TxGraphImpl::ClearChunkData(Entry& entry) noexcept
|
||||||
|
{
|
||||||
|
if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
|
||||||
|
Assume(m_main_chunkindex_observers == 0);
|
||||||
|
// If the Entry has a non-empty m_main_chunkindex_iterator, extract it, and move the handle
|
||||||
|
// to the cache of discarded chunkindex entries.
|
||||||
|
m_main_chunkindex_discarded.emplace_back(m_main_chunkindex.extract(entry.m_main_chunkindex_iterator));
|
||||||
|
entry.m_main_chunkindex_iterator = m_main_chunkindex.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxGraphImpl::CreateChunkData(GraphIndex idx, LinearizationIndex chunk_count) noexcept
|
||||||
|
{
|
||||||
|
auto& entry = m_entries[idx];
|
||||||
|
if (!m_main_chunkindex_discarded.empty()) {
|
||||||
|
// Reuse an discarded node handle.
|
||||||
|
auto& node = m_main_chunkindex_discarded.back().value();
|
||||||
|
node.m_graph_index = idx;
|
||||||
|
node.m_chunk_count = chunk_count;
|
||||||
|
auto insert_result = m_main_chunkindex.insert(std::move(m_main_chunkindex_discarded.back()));
|
||||||
|
Assume(insert_result.inserted);
|
||||||
|
entry.m_main_chunkindex_iterator = insert_result.position;
|
||||||
|
m_main_chunkindex_discarded.pop_back();
|
||||||
|
} else {
|
||||||
|
// Construct a new entry.
|
||||||
|
auto emplace_result = m_main_chunkindex.emplace(idx, chunk_count);
|
||||||
|
Assume(emplace_result.second);
|
||||||
|
entry.m_main_chunkindex_iterator = emplace_result.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TxGraphImpl::ClearLocator(int level, GraphIndex idx) noexcept
|
void TxGraphImpl::ClearLocator(int level, GraphIndex idx) noexcept
|
||||||
{
|
{
|
||||||
auto& entry = m_entries[idx];
|
auto& entry = m_entries[idx];
|
||||||
|
@ -520,6 +646,7 @@ void TxGraphImpl::ClearLocator(int level, GraphIndex idx) noexcept
|
||||||
--m_staging_clusterset->m_txcount;
|
--m_staging_clusterset->m_txcount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (level == 0) ClearChunkData(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Cluster::Updated(TxGraphImpl& graph) noexcept
|
void Cluster::Updated(TxGraphImpl& graph) noexcept
|
||||||
|
@ -527,6 +654,9 @@ void Cluster::Updated(TxGraphImpl& graph) noexcept
|
||||||
// Update all the Locators for this Cluster's Entry objects.
|
// Update all the Locators for this Cluster's Entry objects.
|
||||||
for (DepGraphIndex idx : m_linearization) {
|
for (DepGraphIndex idx : m_linearization) {
|
||||||
auto& entry = graph.m_entries[m_mapping[idx]];
|
auto& entry = graph.m_entries[m_mapping[idx]];
|
||||||
|
// Discard any potential ChunkData prior to modifying the Cluster (as that could
|
||||||
|
// invalidate its ordering).
|
||||||
|
if (m_level == 0) graph.ClearChunkData(entry);
|
||||||
entry.m_locator[m_level].SetPresent(this, idx);
|
entry.m_locator[m_level].SetPresent(this, idx);
|
||||||
}
|
}
|
||||||
// If this is for the main graph (level = 0), and the Cluster's quality is ACCEPTABLE or
|
// If this is for the main graph (level = 0), and the Cluster's quality is ACCEPTABLE or
|
||||||
|
@ -535,14 +665,15 @@ void Cluster::Updated(TxGraphImpl& graph) noexcept
|
||||||
// ACCEPTABLE, so it is pointless to compute these if we haven't reached that quality level
|
// ACCEPTABLE, so it is pointless to compute these if we haven't reached that quality level
|
||||||
// yet.
|
// yet.
|
||||||
if (m_level == 0 && IsAcceptable()) {
|
if (m_level == 0 && IsAcceptable()) {
|
||||||
LinearizationChunking chunking(m_depgraph, m_linearization);
|
const LinearizationChunking chunking(m_depgraph, m_linearization);
|
||||||
LinearizationIndex lin_idx{0};
|
LinearizationIndex lin_idx{0};
|
||||||
// Iterate over the chunks.
|
// Iterate over the chunks.
|
||||||
for (unsigned chunk_idx = 0; chunk_idx < chunking.NumChunksLeft(); ++chunk_idx) {
|
for (unsigned chunk_idx = 0; chunk_idx < chunking.NumChunksLeft(); ++chunk_idx) {
|
||||||
auto chunk = chunking.GetChunk(chunk_idx);
|
auto chunk = chunking.GetChunk(chunk_idx);
|
||||||
Assume(chunk.transactions.Any());
|
auto chunk_count = chunk.transactions.Count();
|
||||||
|
Assume(chunk_count > 0);
|
||||||
// Iterate over the transactions in the linearization, which must match those in chunk.
|
// Iterate over the transactions in the linearization, which must match those in chunk.
|
||||||
do {
|
while (true) {
|
||||||
DepGraphIndex idx = m_linearization[lin_idx];
|
DepGraphIndex idx = m_linearization[lin_idx];
|
||||||
GraphIndex graph_idx = m_mapping[idx];
|
GraphIndex graph_idx = m_mapping[idx];
|
||||||
auto& entry = graph.m_entries[graph_idx];
|
auto& entry = graph.m_entries[graph_idx];
|
||||||
|
@ -550,7 +681,18 @@ void Cluster::Updated(TxGraphImpl& graph) noexcept
|
||||||
entry.m_main_chunk_feerate = FeePerWeight::FromFeeFrac(chunk.feerate);
|
entry.m_main_chunk_feerate = FeePerWeight::FromFeeFrac(chunk.feerate);
|
||||||
Assume(chunk.transactions[idx]);
|
Assume(chunk.transactions[idx]);
|
||||||
chunk.transactions.Reset(idx);
|
chunk.transactions.Reset(idx);
|
||||||
} while(chunk.transactions.Any());
|
if (chunk.transactions.None()) {
|
||||||
|
// Last transaction in the chunk.
|
||||||
|
if (chunk_count == 1 && chunk_idx + 1 == chunking.NumChunksLeft()) {
|
||||||
|
// If this is the final chunk of the cluster, and it contains just a single
|
||||||
|
// transaction (which will always be true for the very common singleton
|
||||||
|
// clusters), store the special value -1 as chunk count.
|
||||||
|
chunk_count = LinearizationIndex(-1);
|
||||||
|
}
|
||||||
|
graph.CreateChunkData(graph_idx, chunk_count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -692,6 +834,13 @@ void Cluster::MoveToMain(TxGraphImpl& graph) noexcept
|
||||||
Updated(graph);
|
Updated(graph);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Cluster::AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept
|
||||||
|
{
|
||||||
|
auto chunk_feerates = ChunkLinearization(m_depgraph, m_linearization);
|
||||||
|
ret.reserve(ret.size() + chunk_feerates.size());
|
||||||
|
ret.insert(ret.end(), chunk_feerates.begin(), chunk_feerates.end());
|
||||||
|
}
|
||||||
|
|
||||||
bool Cluster::Split(TxGraphImpl& graph) noexcept
|
bool Cluster::Split(TxGraphImpl& graph) noexcept
|
||||||
{
|
{
|
||||||
// This function can only be called when the Cluster needs splitting.
|
// This function can only be called when the Cluster needs splitting.
|
||||||
|
@ -798,7 +947,11 @@ void Cluster::Merge(TxGraphImpl& graph, Cluster& other) noexcept
|
||||||
// Update the transaction's Locator. There is no need to call Updated() to update chunk
|
// Update the transaction's Locator. There is no need to call Updated() to update chunk
|
||||||
// feerates, as Updated() will be invoked by Cluster::ApplyDependencies on the resulting
|
// feerates, as Updated() will be invoked by Cluster::ApplyDependencies on the resulting
|
||||||
// merged Cluster later anyway).
|
// merged Cluster later anyway).
|
||||||
graph.m_entries[idx].m_locator[m_level].SetPresent(this, new_pos);
|
auto& entry = graph.m_entries[idx];
|
||||||
|
// Discard any potential ChunkData prior to modifying the Cluster (as that could
|
||||||
|
// invalidate its ordering).
|
||||||
|
if (m_level == 0) graph.ClearChunkData(entry);
|
||||||
|
entry.m_locator[m_level].SetPresent(this, new_pos);
|
||||||
}
|
}
|
||||||
// Purge the other Cluster, now that everything has been moved.
|
// Purge the other Cluster, now that everything has been moved.
|
||||||
other.m_depgraph = DepGraph<SetType>{};
|
other.m_depgraph = DepGraph<SetType>{};
|
||||||
|
@ -1012,6 +1165,10 @@ void TxGraphImpl::SwapIndexes(GraphIndex a, GraphIndex b) noexcept
|
||||||
Entry& entry = m_entries[idx];
|
Entry& entry = m_entries[idx];
|
||||||
// Update linked Ref, if any exists.
|
// Update linked Ref, if any exists.
|
||||||
if (entry.m_ref) GetRefIndex(*entry.m_ref) = idx;
|
if (entry.m_ref) GetRefIndex(*entry.m_ref) = idx;
|
||||||
|
// Update linked chunk index entries, if any exist.
|
||||||
|
if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
|
||||||
|
entry.m_main_chunkindex_iterator->m_graph_index = idx;
|
||||||
|
}
|
||||||
// Update the locators for both levels. The rest of the Entry information will not change,
|
// Update the locators for both levels. The rest of the Entry information will not change,
|
||||||
// so no need to invoke Cluster::Updated().
|
// so no need to invoke Cluster::Updated().
|
||||||
for (int level = 0; level < MAX_LEVELS; ++level) {
|
for (int level = 0; level < MAX_LEVELS; ++level) {
|
||||||
|
@ -1036,6 +1193,9 @@ void TxGraphImpl::Compact() noexcept
|
||||||
if (!m_staging_clusterset->m_removed.empty()) return;
|
if (!m_staging_clusterset->m_removed.empty()) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Release memory used by discarded ChunkData index entries.
|
||||||
|
ClearShrink(m_main_chunkindex_discarded);
|
||||||
|
|
||||||
// Sort the GraphIndexes that need to be cleaned up. They are sorted in reverse, so the last
|
// Sort the GraphIndexes that need to be cleaned up. They are sorted in reverse, so the last
|
||||||
// ones get processed first. This means earlier-processed GraphIndexes will not cause moving of
|
// ones get processed first. This means earlier-processed GraphIndexes will not cause moving of
|
||||||
// later-processed ones during the "swap with end of m_entries" step below (which might
|
// later-processed ones during the "swap with end of m_entries" step below (which might
|
||||||
|
@ -1415,12 +1575,14 @@ Cluster::Cluster(uint64_t sequence, TxGraphImpl& graph, const FeePerWeight& feer
|
||||||
|
|
||||||
TxGraph::Ref TxGraphImpl::AddTransaction(const FeePerWeight& feerate) noexcept
|
TxGraph::Ref TxGraphImpl::AddTransaction(const FeePerWeight& feerate) noexcept
|
||||||
{
|
{
|
||||||
|
Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
|
||||||
// Construct a new Ref.
|
// Construct a new Ref.
|
||||||
Ref ret;
|
Ref ret;
|
||||||
// Construct a new Entry, and link it with the Ref.
|
// Construct a new Entry, and link it with the Ref.
|
||||||
auto idx = m_entries.size();
|
auto idx = m_entries.size();
|
||||||
m_entries.emplace_back();
|
m_entries.emplace_back();
|
||||||
auto& entry = m_entries.back();
|
auto& entry = m_entries.back();
|
||||||
|
entry.m_main_chunkindex_iterator = m_main_chunkindex.end();
|
||||||
entry.m_ref = &ret;
|
entry.m_ref = &ret;
|
||||||
GetRefGraph(ret) = this;
|
GetRefGraph(ret) = this;
|
||||||
GetRefIndex(ret) = idx;
|
GetRefIndex(ret) = idx;
|
||||||
|
@ -1442,6 +1604,7 @@ void TxGraphImpl::RemoveTransaction(const Ref& arg) noexcept
|
||||||
// having been removed).
|
// having been removed).
|
||||||
if (GetRefGraph(arg) == nullptr) return;
|
if (GetRefGraph(arg) == nullptr) return;
|
||||||
Assume(GetRefGraph(arg) == this);
|
Assume(GetRefGraph(arg) == this);
|
||||||
|
Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
|
||||||
// Find the Cluster the transaction is in, and stop if it isn't in any.
|
// Find the Cluster the transaction is in, and stop if it isn't in any.
|
||||||
int level = GetTopLevel();
|
int level = GetTopLevel();
|
||||||
auto cluster = FindCluster(GetRefIndex(arg), level);
|
auto cluster = FindCluster(GetRefIndex(arg), level);
|
||||||
|
@ -1460,6 +1623,7 @@ void TxGraphImpl::AddDependency(const Ref& parent, const Ref& child) noexcept
|
||||||
// removed).
|
// removed).
|
||||||
if (GetRefGraph(parent) == nullptr || GetRefGraph(child) == nullptr) return;
|
if (GetRefGraph(parent) == nullptr || GetRefGraph(child) == nullptr) return;
|
||||||
Assume(GetRefGraph(parent) == this && GetRefGraph(child) == this);
|
Assume(GetRefGraph(parent) == this && GetRefGraph(child) == this);
|
||||||
|
Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
|
||||||
// Don't do anything if this is a dependency on self.
|
// Don't do anything if this is a dependency on self.
|
||||||
if (GetRefIndex(parent) == GetRefIndex(child)) return;
|
if (GetRefIndex(parent) == GetRefIndex(child)) return;
|
||||||
// Find the Cluster the parent and child transaction are in, and stop if either appears to be
|
// Find the Cluster the parent and child transaction are in, and stop if either appears to be
|
||||||
|
@ -1526,17 +1690,18 @@ void Cluster::GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TxGraph::Ref*> Cluster::GetClusterRefs(const TxGraphImpl& graph) noexcept
|
bool Cluster::GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept
|
||||||
{
|
{
|
||||||
std::vector<TxGraph::Ref*> ret;
|
// Translate the transactions in the Cluster (in linearization order, starting at start_pos in
|
||||||
ret.reserve(m_linearization.size());
|
// the linearization) to Refs, and fill them in range.
|
||||||
// Translate all transactions in the Cluster (in linearization order) to Refs.
|
for (auto& ref : range) {
|
||||||
for (auto idx : m_linearization) {
|
Assume(start_pos < m_linearization.size());
|
||||||
const auto& entry = graph.m_entries[m_mapping[idx]];
|
const auto& entry = graph.m_entries[m_mapping[m_linearization[start_pos++]]];
|
||||||
Assume(entry.m_ref != nullptr);
|
Assume(entry.m_ref != nullptr);
|
||||||
ret.push_back(entry.m_ref);
|
ref = entry.m_ref;
|
||||||
}
|
}
|
||||||
return ret;
|
// Return whether start_pos has advanced to the end of the Cluster.
|
||||||
|
return start_pos == m_linearization.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
FeePerWeight Cluster::GetIndividualFeerate(DepGraphIndex idx) noexcept
|
FeePerWeight Cluster::GetIndividualFeerate(DepGraphIndex idx) noexcept
|
||||||
|
@ -1680,7 +1845,9 @@ std::vector<TxGraph::Ref*> TxGraphImpl::GetCluster(const Ref& arg, bool main_onl
|
||||||
if (cluster == nullptr) return {};
|
if (cluster == nullptr) return {};
|
||||||
// Make sure the Cluster has an acceptable quality level, and then dispatch to it.
|
// Make sure the Cluster has an acceptable quality level, and then dispatch to it.
|
||||||
MakeAcceptable(*cluster);
|
MakeAcceptable(*cluster);
|
||||||
return cluster->GetClusterRefs(*this);
|
std::vector<TxGraph::Ref*> ret(cluster->GetTxCount());
|
||||||
|
cluster->GetClusterRefs(*this, ret, 0);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
TxGraph::GraphIndex TxGraphImpl::GetTransactionCount(bool main_only) noexcept
|
TxGraph::GraphIndex TxGraphImpl::GetTransactionCount(bool main_only) noexcept
|
||||||
|
@ -1798,6 +1965,7 @@ void TxGraphImpl::CommitStaging() noexcept
|
||||||
{
|
{
|
||||||
// Staging must exist.
|
// Staging must exist.
|
||||||
Assume(m_staging_clusterset.has_value());
|
Assume(m_staging_clusterset.has_value());
|
||||||
|
Assume(m_main_chunkindex_observers == 0);
|
||||||
// Delete all conflicting Clusters in main, to make place for moving the staging ones
|
// Delete all conflicting Clusters in main, to make place for moving the staging ones
|
||||||
// there. All of these have been copied to staging in PullIn().
|
// there. All of these have been copied to staging in PullIn().
|
||||||
auto conflicts = GetConflicts();
|
auto conflicts = GetConflicts();
|
||||||
|
@ -1850,6 +2018,7 @@ void TxGraphImpl::SetTransactionFee(const Ref& ref, int64_t fee) noexcept
|
||||||
// Don't do anything if the passed Ref is empty.
|
// Don't do anything if the passed Ref is empty.
|
||||||
if (GetRefGraph(ref) == nullptr) return;
|
if (GetRefGraph(ref) == nullptr) return;
|
||||||
Assume(GetRefGraph(ref) == this);
|
Assume(GetRefGraph(ref) == this);
|
||||||
|
Assume(m_main_chunkindex_observers == 0);
|
||||||
// Find the entry, its locator, and inform its Cluster about the new feerate, if any.
|
// Find the entry, its locator, and inform its Cluster about the new feerate, if any.
|
||||||
auto& entry = m_entries[GetRefIndex(ref)];
|
auto& entry = m_entries[GetRefIndex(ref)];
|
||||||
for (int level = 0; level < MAX_LEVELS; ++level) {
|
for (int level = 0; level < MAX_LEVELS; ++level) {
|
||||||
|
@ -1916,6 +2085,32 @@ TxGraph::GraphIndex TxGraphImpl::CountDistinctClusters(std::span<const Ref* cons
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>> TxGraphImpl::GetMainStagingDiagrams() noexcept
|
||||||
|
{
|
||||||
|
Assume(m_staging_clusterset.has_value());
|
||||||
|
MakeAllAcceptable(0);
|
||||||
|
Assume(m_main_clusterset.m_deps_to_add.empty()); // can only fail if main is oversized
|
||||||
|
MakeAllAcceptable(1);
|
||||||
|
Assume(m_staging_clusterset->m_deps_to_add.empty()); // can only fail if staging is oversized
|
||||||
|
// For all Clusters in main which conflict with Clusters in staging (i.e., all that are removed
|
||||||
|
// by, or replaced in, staging), gather their chunk feerates.
|
||||||
|
auto main_clusters = GetConflicts();
|
||||||
|
std::vector<FeeFrac> main_feerates, staging_feerates;
|
||||||
|
for (Cluster* cluster : main_clusters) {
|
||||||
|
cluster->AppendChunkFeerates(main_feerates);
|
||||||
|
}
|
||||||
|
// Do the same for the Clusters in staging themselves.
|
||||||
|
for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
|
||||||
|
for (const auto& cluster : m_staging_clusterset->m_clusters[quality]) {
|
||||||
|
cluster->AppendChunkFeerates(staging_feerates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort both by decreasing feerate to obtain diagrams, and return them.
|
||||||
|
std::sort(main_feerates.begin(), main_feerates.end(), [](auto& a, auto& b) { return a > b; });
|
||||||
|
std::sort(staging_feerates.begin(), staging_feerates.end(), [](auto& a, auto& b) { return a > b; });
|
||||||
|
return std::make_pair(std::move(main_feerates), std::move(staging_feerates));
|
||||||
|
}
|
||||||
|
|
||||||
void Cluster::SanityCheck(const TxGraphImpl& graph, int level) const
|
void Cluster::SanityCheck(const TxGraphImpl& graph, int level) const
|
||||||
{
|
{
|
||||||
// There must be an m_mapping for each m_depgraph position (including holes).
|
// There must be an m_mapping for each m_depgraph position (including holes).
|
||||||
|
@ -1934,6 +2129,7 @@ void Cluster::SanityCheck(const TxGraphImpl& graph, int level) const
|
||||||
// Verify m_linearization.
|
// Verify m_linearization.
|
||||||
SetType m_done;
|
SetType m_done;
|
||||||
LinearizationIndex linindex{0};
|
LinearizationIndex linindex{0};
|
||||||
|
DepGraphIndex chunk_pos{0}; //!< position within the current chunk
|
||||||
assert(m_depgraph.IsAcyclic());
|
assert(m_depgraph.IsAcyclic());
|
||||||
for (auto lin_pos : m_linearization) {
|
for (auto lin_pos : m_linearization) {
|
||||||
assert(lin_pos < m_mapping.size());
|
assert(lin_pos < m_mapping.size());
|
||||||
|
@ -1950,8 +2146,21 @@ void Cluster::SanityCheck(const TxGraphImpl& graph, int level) const
|
||||||
++linindex;
|
++linindex;
|
||||||
if (!linchunking.GetChunk(0).transactions[lin_pos]) {
|
if (!linchunking.GetChunk(0).transactions[lin_pos]) {
|
||||||
linchunking.MarkDone(linchunking.GetChunk(0).transactions);
|
linchunking.MarkDone(linchunking.GetChunk(0).transactions);
|
||||||
|
chunk_pos = 0;
|
||||||
}
|
}
|
||||||
assert(entry.m_main_chunk_feerate == linchunking.GetChunk(0).feerate);
|
assert(entry.m_main_chunk_feerate == linchunking.GetChunk(0).feerate);
|
||||||
|
// Verify that an entry in the chunk index exists for every chunk-ending transaction.
|
||||||
|
++chunk_pos;
|
||||||
|
bool is_chunk_end = (chunk_pos == linchunking.GetChunk(0).transactions.Count());
|
||||||
|
assert((entry.m_main_chunkindex_iterator != graph.m_main_chunkindex.end()) == is_chunk_end);
|
||||||
|
if (is_chunk_end) {
|
||||||
|
auto& chunk_data = *entry.m_main_chunkindex_iterator;
|
||||||
|
if (m_done == m_depgraph.Positions() && chunk_pos == 1) {
|
||||||
|
assert(chunk_data.m_chunk_count == LinearizationIndex(-1));
|
||||||
|
} else {
|
||||||
|
assert(chunk_data.m_chunk_count == chunk_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
// If this Cluster has an acceptable quality level, its chunks must be connected.
|
// If this Cluster has an acceptable quality level, its chunks must be connected.
|
||||||
assert(m_depgraph.IsConnected(linchunking.GetChunk(0).transactions));
|
assert(m_depgraph.IsConnected(linchunking.GetChunk(0).transactions));
|
||||||
}
|
}
|
||||||
|
@ -1970,6 +2179,8 @@ void TxGraphImpl::SanityCheck() const
|
||||||
std::set<GraphIndex> expected_removed[MAX_LEVELS];
|
std::set<GraphIndex> expected_removed[MAX_LEVELS];
|
||||||
/** Which Cluster::m_sequence values have been encountered. */
|
/** Which Cluster::m_sequence values have been encountered. */
|
||||||
std::set<uint64_t> sequences;
|
std::set<uint64_t> sequences;
|
||||||
|
/** Which GraphIndexes ought to occur in m_main_chunkindex, based on m_entries. */
|
||||||
|
std::set<GraphIndex> expected_chunkindex;
|
||||||
/** Whether compaction is possible in the current state. */
|
/** Whether compaction is possible in the current state. */
|
||||||
bool compact_possible{true};
|
bool compact_possible{true};
|
||||||
|
|
||||||
|
@ -1984,6 +2195,11 @@ void TxGraphImpl::SanityCheck() const
|
||||||
assert(GetRefGraph(*entry.m_ref) == this);
|
assert(GetRefGraph(*entry.m_ref) == this);
|
||||||
assert(GetRefIndex(*entry.m_ref) == idx);
|
assert(GetRefIndex(*entry.m_ref) == idx);
|
||||||
}
|
}
|
||||||
|
if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
|
||||||
|
// Remember which entries we see a chunkindex entry for.
|
||||||
|
assert(entry.m_locator[0].IsPresent());
|
||||||
|
expected_chunkindex.insert(idx);
|
||||||
|
}
|
||||||
// Verify the Entry m_locators.
|
// Verify the Entry m_locators.
|
||||||
bool was_present{false}, was_removed{false};
|
bool was_present{false}, was_removed{false};
|
||||||
for (int level = 0; level < MAX_LEVELS; ++level) {
|
for (int level = 0; level < MAX_LEVELS; ++level) {
|
||||||
|
@ -2096,14 +2312,157 @@ void TxGraphImpl::SanityCheck() const
|
||||||
if (compact_possible) {
|
if (compact_possible) {
|
||||||
assert(actual_unlinked.empty());
|
assert(actual_unlinked.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finally, check the chunk index.
|
||||||
|
std::set<GraphIndex> actual_chunkindex;
|
||||||
|
FeeFrac last_chunk_feerate;
|
||||||
|
for (const auto& chunk : m_main_chunkindex) {
|
||||||
|
GraphIndex idx = chunk.m_graph_index;
|
||||||
|
actual_chunkindex.insert(idx);
|
||||||
|
auto chunk_feerate = m_entries[idx].m_main_chunk_feerate;
|
||||||
|
if (!last_chunk_feerate.IsEmpty()) {
|
||||||
|
assert(FeeRateCompare(last_chunk_feerate, chunk_feerate) >= 0);
|
||||||
|
}
|
||||||
|
last_chunk_feerate = chunk_feerate;
|
||||||
|
}
|
||||||
|
assert(actual_chunkindex == expected_chunkindex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxGraphImpl::DoWork() noexcept
|
void TxGraphImpl::DoWork() noexcept
|
||||||
{
|
{
|
||||||
for (int level = 0; level <= GetTopLevel(); ++level) {
|
for (int level = 0; level <= GetTopLevel(); ++level) {
|
||||||
|
if (level > 0 || m_main_chunkindex_observers == 0) {
|
||||||
MakeAllAcceptable(level);
|
MakeAllAcceptable(level);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlockBuilderImpl::Next() noexcept
|
||||||
|
{
|
||||||
|
// Don't do anything if we're already done.
|
||||||
|
if (m_cur_iter == m_graph->m_main_chunkindex.end()) return;
|
||||||
|
while (true) {
|
||||||
|
// Advance the pointer, and stop if we reach the end.
|
||||||
|
++m_cur_iter;
|
||||||
|
m_cur_cluster = nullptr;
|
||||||
|
if (m_cur_iter == m_graph->m_main_chunkindex.end()) break;
|
||||||
|
// Find the cluster pointed to by m_cur_iter.
|
||||||
|
const auto& chunk_data = *m_cur_iter;
|
||||||
|
const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
|
||||||
|
m_cur_cluster = chunk_end_entry.m_locator[0].cluster;
|
||||||
|
// If we previously skipped a chunk from this cluster we cannot include more from it.
|
||||||
|
if (!m_excluded_clusters.contains(m_cur_cluster)) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> BlockBuilderImpl::GetCurrentChunk() noexcept
|
||||||
|
{
|
||||||
|
std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> ret;
|
||||||
|
// Populate the return value if we are not done.
|
||||||
|
if (m_cur_iter != m_graph->m_main_chunkindex.end()) {
|
||||||
|
ret.emplace();
|
||||||
|
const auto& chunk_data = *m_cur_iter;
|
||||||
|
const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
|
||||||
|
auto cluster = chunk_end_entry.m_locator[0].cluster;
|
||||||
|
if (chunk_data.m_chunk_count == LinearizationIndex(-1)) {
|
||||||
|
// Special case in case just a single transaction remains, avoiding the need to
|
||||||
|
// dispatch to and dereference Cluster.
|
||||||
|
ret->first.resize(1);
|
||||||
|
Assume(chunk_end_entry.m_ref != nullptr);
|
||||||
|
ret->first[0] = chunk_end_entry.m_ref;
|
||||||
|
m_cur_cluster = nullptr;
|
||||||
|
} else {
|
||||||
|
ret->first.resize(chunk_data.m_chunk_count);
|
||||||
|
auto start_pos = chunk_end_entry.m_main_lin_index + 1 - chunk_data.m_chunk_count;
|
||||||
|
bool is_end = cluster->GetClusterRefs(*m_graph, ret->first, start_pos);
|
||||||
|
if (is_end) {
|
||||||
|
m_cur_cluster = nullptr;
|
||||||
|
// If the chunk size was 1, then the special case above should have been used.
|
||||||
|
Assume(chunk_data.m_chunk_count > 1);
|
||||||
|
} else {
|
||||||
|
Assume(cluster == m_cur_cluster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret->second = chunk_end_entry.m_main_chunk_feerate;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockBuilderImpl::BlockBuilderImpl(TxGraphImpl& graph) noexcept : m_graph(&graph)
|
||||||
|
{
|
||||||
|
// Make sure all clusters in main are up to date, and acceptable.
|
||||||
|
m_graph->MakeAllAcceptable(0);
|
||||||
|
// There cannot remain any inapplicable dependencies (only possible if main is oversized).
|
||||||
|
Assume(m_graph->m_main_clusterset.m_deps_to_add.empty());
|
||||||
|
// Remember that this object is observing the graph's index, so that we can detect concurrent
|
||||||
|
// modifications.
|
||||||
|
++m_graph->m_main_chunkindex_observers;
|
||||||
|
// Find the first chunk.
|
||||||
|
m_cur_iter = m_graph->m_main_chunkindex.begin();
|
||||||
|
m_cur_cluster = nullptr;
|
||||||
|
if (m_cur_iter != m_graph->m_main_chunkindex.end()) {
|
||||||
|
// Find the cluster pointed to by m_cur_iter.
|
||||||
|
const auto& chunk_data = *m_cur_iter;
|
||||||
|
const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
|
||||||
|
m_cur_cluster = chunk_end_entry.m_locator[0].cluster;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockBuilderImpl::~BlockBuilderImpl()
|
||||||
|
{
|
||||||
|
Assume(m_graph->m_main_chunkindex_observers > 0);
|
||||||
|
// Permit modifications to the main graph again after destroying the BlockBuilderImpl.
|
||||||
|
--m_graph->m_main_chunkindex_observers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlockBuilderImpl::Include() noexcept
|
||||||
|
{
|
||||||
|
// The actual inclusion of the chunk is done by the calling code. All we have to do is switch
|
||||||
|
// to the next chunk.
|
||||||
|
Next();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlockBuilderImpl::Skip() noexcept
|
||||||
|
{
|
||||||
|
// When skipping a chunk we need to not include anything more of the cluster, as that could make
|
||||||
|
// the result topologically invalid. Note that m_cur_cluster == nullptr when this was the last
|
||||||
|
// chunk of a cluster, so in that case nothing is added here. This may significantly reduce the
|
||||||
|
// size of m_excluded_clusters, especially when many singleton clusters are ignored.
|
||||||
|
if (m_cur_cluster != nullptr) m_excluded_clusters.insert(m_cur_cluster);
|
||||||
|
Next();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<TxGraph::BlockBuilder> TxGraphImpl::GetBlockBuilder() noexcept
|
||||||
|
{
|
||||||
|
return std::make_unique<BlockBuilderImpl>(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::vector<TxGraph::Ref*>, FeePerWeight> TxGraphImpl::GetWorstMainChunk() noexcept
|
||||||
|
{
|
||||||
|
std::pair<std::vector<Ref*>, 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;
|
||||||
|
if (chunk_data.m_chunk_count == LinearizationIndex(-1) || chunk_data.m_chunk_count == 1) {
|
||||||
|
// Special case for singletons.
|
||||||
|
ret.first.resize(1);
|
||||||
|
Assume(chunk_end_entry.m_ref != nullptr);
|
||||||
|
ret.first[0] = chunk_end_entry.m_ref;
|
||||||
|
} else {
|
||||||
|
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
|
} // namespace
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#include <compare>
|
#include <compare>
|
||||||
#include <stdint.h>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdint.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <util/feefrac.h>
|
#include <util/feefrac.h>
|
||||||
|
|
||||||
|
@ -162,6 +164,39 @@ public:
|
||||||
* main clusters are counted. Refs that do not exist in the queried graph are ignored. Refs
|
* main clusters are counted. Refs that do not exist in the queried graph are ignored. Refs
|
||||||
* can not be null. The queried graph must not be oversized. */
|
* can not be null. The queried graph must not be oversized. */
|
||||||
virtual GraphIndex CountDistinctClusters(std::span<const Ref* const>, bool main_only = false) noexcept = 0;
|
virtual GraphIndex CountDistinctClusters(std::span<const Ref* const>, bool main_only = false) noexcept = 0;
|
||||||
|
/** For both main and staging (which must both exist and not be oversized), return the combined
|
||||||
|
* respective feerate diagrams, including chunks from all clusters, but excluding clusters
|
||||||
|
* that appear identically in both. Use FeeFrac rather than FeePerWeight so CompareChunks is
|
||||||
|
* usable without type-conversion. */
|
||||||
|
virtual std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>> GetMainStagingDiagrams() noexcept = 0;
|
||||||
|
|
||||||
|
/** Interface returned by GetBlockBuilder. */
|
||||||
|
class BlockBuilder
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
/** Make constructor non-public (use TxGraph::GetBlockBuilder()). */
|
||||||
|
BlockBuilder() noexcept = default;
|
||||||
|
public:
|
||||||
|
/** Support safe inheritance. */
|
||||||
|
virtual ~BlockBuilder() = default;
|
||||||
|
/** Get the chunk that is currently suggested to be included, plus its feerate, if any. */
|
||||||
|
virtual std::optional<std::pair<std::vector<Ref*>, FeePerWeight>> GetCurrentChunk() noexcept = 0;
|
||||||
|
/** Mark the current chunk as included, and progress to the next one. */
|
||||||
|
virtual void Include() noexcept = 0;
|
||||||
|
/** Mark the current chunk as skipped, and progress to the next one. Further chunks from
|
||||||
|
* the same cluster as the current one will not be reported anymore. */
|
||||||
|
virtual void Skip() noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Construct a block builder, drawing chunks in order, from the main graph, which cannot be
|
||||||
|
* 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<BlockBuilder> 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<std::vector<Ref*>, FeePerWeight> GetWorstMainChunk() noexcept = 0;
|
||||||
|
|
||||||
/** Perform an internal consistency check on this object. */
|
/** Perform an internal consistency check on this object. */
|
||||||
virtual void SanityCheck() const = 0;
|
virtual void SanityCheck() const = 0;
|
||||||
|
|
Loading…
Add table
Reference in a new issue