Skip to content

Commit b2ea365

Browse files
committed
txgraph: Add Get{Ancestors,Descendants}Union functions (feature)
Like GetAncestors and GetDescendants, but for the union of multiple inputs.
1 parent 54bcedd commit b2ea365

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

src/test/fuzz/txgraph.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,28 @@ FUZZ_TARGET(txgraph)
454454
auto expect_set = sel_sim.GetAncDesc(ref, alt);
455455
assert(result_set == expect_set);
456456
break;
457+
} else if (!sel_sim.IsOversized() && command-- == 0) {
458+
// GetAncestorsUnion/GetDescendantsUnion.
459+
std::vector<TxGraph::Ref*> refs;
460+
// Gather a list of up to 15 Ref pointers.
461+
auto count = provider.ConsumeIntegralInRange<size_t>(0, 15);
462+
refs.resize(count);
463+
for (size_t i = 0; i < count; ++i) {
464+
refs[i] = pick_fn();
465+
}
466+
// Their order should not matter, shuffle them.
467+
std::shuffle(refs.begin(), refs.end(), rng);
468+
// Invoke the real function, and convert to SimPos set.
469+
auto result = alt ? real->GetDescendantsUnion(refs, use_main)
470+
: real->GetAncestorsUnion(refs, use_main);
471+
auto result_set = sel_sim.MakeSet(result);
472+
assert(result.size() == result_set.Count());
473+
// Compute the expected result.
474+
SimTxGraph::SetType expect_set;
475+
for (TxGraph::Ref* ref : refs) expect_set |= sel_sim.GetAncDesc(ref, alt);
476+
// Compare.
477+
assert(result_set == expect_set);
478+
break;
457479
} else if (!sel_sim.IsOversized() && command-- == 0) {
458480
// GetCluster.
459481
auto ref = pick_fn();

src/txgraph.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,8 @@ class TxGraphImpl final : public TxGraph
453453
std::vector<Ref*> GetCluster(const Ref& arg, bool main_only = false) noexcept final;
454454
std::vector<Ref*> GetAncestors(const Ref& arg, bool main_only = false) noexcept final;
455455
std::vector<Ref*> GetDescendants(const Ref& arg, bool main_only = false) noexcept final;
456+
std::vector<Ref*> GetAncestorsUnion(std::span<const Ref* const> args, bool main_only = false) noexcept final;
457+
std::vector<Ref*> GetDescendantsUnion(std::span<const Ref* const> args, bool main_only = false) noexcept final;
456458
GraphIndex GetTransactionCount(bool main_only = false) noexcept final;
457459
bool IsOversized(bool main_only = false) noexcept final;
458460
std::strong_ordering CompareMainOrder(const Ref& a, const Ref& b) noexcept final;
@@ -1581,6 +1583,70 @@ std::vector<TxGraph::Ref*> TxGraphImpl::GetDescendants(const Ref& arg, bool main
15811583
return ret;
15821584
}
15831585

1586+
std::vector<TxGraph::Ref*> TxGraphImpl::GetAncestorsUnion(std::span<const Ref* const> args, bool main_only) noexcept
1587+
{
1588+
// Apply all dependencies, as the result might be incorrect otherwise.
1589+
size_t level = GetSpecifiedLevel(main_only);
1590+
ApplyDependencies(level);
1591+
// Ancestry cannot be known if unapplied dependencies remain.
1592+
Assume(GetClusterSet(level).m_deps_to_add.empty());
1593+
1594+
// Translate args to matches.
1595+
std::vector<std::pair<Cluster*, DepGraphIndex>> matches;
1596+
matches.reserve(args.size());
1597+
for (auto arg : args) {
1598+
// Skip empty Refs.
1599+
if (GetRefGraph(*arg) == nullptr) continue;
1600+
Assume(GetRefGraph(*arg) == this);
1601+
// Find the Cluster the argument is in, and skip if none is found.
1602+
auto cluster = FindCluster(GetRefIndex(*arg), level);
1603+
if (cluster == nullptr) continue;
1604+
// Append to matches.
1605+
matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster->m_level].index);
1606+
}
1607+
// Group by Cluster.
1608+
std::sort(matches.begin(), matches.end(), [](auto& a, auto& b) noexcept { return std::less{}(a.first, b.first); });
1609+
// Dispatch to the Clusters.
1610+
std::span match_span(matches);
1611+
std::vector<TxGraph::Ref*> ret;
1612+
while (!match_span.empty()) {
1613+
match_span.front().first->GetAncestorRefs(*this, match_span, ret);
1614+
}
1615+
return ret;
1616+
}
1617+
1618+
std::vector<TxGraph::Ref*> TxGraphImpl::GetDescendantsUnion(std::span<const Ref* const> args, bool main_only) noexcept
1619+
{
1620+
// Apply all dependencies, as the result might be incorrect otherwise.
1621+
size_t level = GetSpecifiedLevel(main_only);
1622+
ApplyDependencies(level);
1623+
// Ancestry cannot be known if unapplied dependencies remain.
1624+
Assume(GetClusterSet(level).m_deps_to_add.empty());
1625+
1626+
// Translate args to matches.
1627+
std::vector<std::pair<Cluster*, DepGraphIndex>> matches;
1628+
matches.reserve(args.size());
1629+
for (auto arg : args) {
1630+
// Skip empty Refs.
1631+
if (GetRefGraph(*arg) == nullptr) continue;
1632+
Assume(GetRefGraph(*arg) == this);
1633+
// Find the Cluster the argument is in, and skip if none is found.
1634+
auto cluster = FindCluster(GetRefIndex(*arg), level);
1635+
if (cluster == nullptr) continue;
1636+
// Append to matches.
1637+
matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster->m_level].index);
1638+
}
1639+
// Group by Cluster.
1640+
std::sort(matches.begin(), matches.end(), [](auto& a, auto& b) noexcept { return std::less{}(a.first, b.first); });
1641+
// Dispatch to the Clusters.
1642+
std::span match_span(matches);
1643+
std::vector<TxGraph::Ref*> ret;
1644+
while (!match_span.empty()) {
1645+
match_span.front().first->GetDescendantRefs(*this, match_span, ret);
1646+
}
1647+
return ret;
1648+
}
1649+
15841650
std::vector<TxGraph::Ref*> TxGraphImpl::GetCluster(const Ref& arg, bool main_only) noexcept
15851651
{
15861652
// Return the empty vector if the Ref is empty (which may be indicative of the transaction

src/txgraph.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ class TxGraph
142142
* queried; otherwise the main graph is queried. The queried graph must not be oversized.
143143
* Returns {} if arg does not exist in the graph. */
144144
virtual std::vector<Ref*> GetDescendants(const Ref& arg, bool main_only = false) noexcept = 0;
145+
/** Like GetAncestors, but return the Refs for all transactions in the union of the provided
146+
* arguments' ancestors (each transaction is only reported once). Refs that do not exist in
147+
* the queried graph are ignored. */
148+
virtual std::vector<Ref*> GetAncestorsUnion(std::span<const Ref* const> args, bool main_only = false) noexcept = 0;
149+
/** Like GetDescendants, but return the Refs for all transactions in the union of the provided
150+
* arguments' descendants (each transaction is only reported once). Refs that do not exist in
151+
* the queried graph are ignored. */
152+
virtual std::vector<Ref*> GetDescendantsUnion(std::span<const Ref* const> args, bool main_only = false) noexcept = 0;
145153
/** Get the total number of transactions in the graph. If main_only is false and a staging
146154
* graph exists, it is queried; otherwise the main graph is queried. This is available even
147155
* for oversized graphs. */

0 commit comments

Comments
 (0)