Skip to content

Commit 1d27b74

Browse files
committed
txgraph: Add GetChunkFeerate function (feature)
This adds a function to query the chunk feerate of a transaction, by caching it inside the Entry objects.
1 parent c80aecc commit 1d27b74

File tree

3 files changed

+71
-2
lines changed

3 files changed

+71
-2
lines changed

src/test/fuzz/txgraph.cpp

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,19 @@ FUZZ_TARGET(txgraph)
321321
assert(feerate == sim.graph.FeeRate(simpos));
322322
}
323323
break;
324+
} else if (command-- == 0) {
325+
// GetChunkFeerate.
326+
auto ref = pick_fn();
327+
auto feerate = real->GetChunkFeerate(*ref);
328+
auto simpos = sim.Find(ref);
329+
if (simpos == SimTxGraph::MISSING) {
330+
assert(feerate.IsEmpty());
331+
} else {
332+
// Just do some quick checks that the reported value is in range. A full
333+
// recomputation of expected chunk feerates is done at the end.
334+
assert(feerate.size >= sim.graph.FeeRate(simpos).size);
335+
}
336+
break;
324337
} else if (command-- == 0) {
325338
// GetAncestors/GetDescendants.
326339
auto ref = pick_fn();
@@ -405,13 +418,21 @@ FUZZ_TARGET(txgraph)
405418
simlin.push_back(simpos);
406419
}
407420
// Construct a chunking object for the simulated graph, using the reported cluster
408-
// linearization as ordering.
421+
// linearization as ordering, and compare it against the reported chunk feerates.
409422
cluster_linearize::LinearizationChunking simlinchunk(sim.graph, simlin);
423+
DepGraphIndex idx{0};
410424
for (unsigned chunknum = 0; chunknum < simlinchunk.NumChunksLeft(); ++chunknum) {
411425
auto chunk = simlinchunk.GetChunk(chunknum);
412426
// Require that the chunks of cluster linearizations are connected (this must
413427
// be the case as all linearizations inside are PostLinearized).
414428
assert(sim.graph.IsConnected(chunk.transactions));
429+
// Check the chunk feerates of all transactions in the cluster.
430+
while (chunk.transactions.Any()) {
431+
assert(chunk.transactions[simlin[idx]]);
432+
chunk.transactions.Reset(simlin[idx]);
433+
assert(chunk.feerate == real->GetChunkFeerate(*cluster[idx]));
434+
++idx;
435+
}
415436
}
416437
}
417438
}

src/txgraph.cpp

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ class TxGraphImpl final : public TxGraph
221221
Ref* m_ref{nullptr};
222222
/** Which Cluster and position therein this Entry appears in. */
223223
Locator m_locator;
224+
/** The chunk feerate of this transaction (if not missing). */
225+
FeePerWeight m_chunk_feerate;
224226
};
225227

226228
/** The set of all transactions. GraphIndex values index into this. */
@@ -301,6 +303,7 @@ class TxGraphImpl final : public TxGraph
301303
void SetTransactionFee(const Ref&, int64_t fee) noexcept final;
302304

303305
bool Exists(const Ref& arg) noexcept final;
306+
FeePerWeight GetChunkFeerate(const Ref& arg) noexcept final;
304307
FeePerWeight GetIndividualFeerate(const Ref& arg) noexcept final;
305308
std::vector<Ref*> GetCluster(const Ref& arg) noexcept final;
306309
std::vector<Ref*> GetAncestors(const Ref& arg) noexcept final;
@@ -317,6 +320,24 @@ void Cluster::Updated(TxGraphImpl& graph) noexcept
317320
auto& entry = graph.m_entries[m_mapping[idx]];
318321
entry.m_locator.SetPresent(this, idx);
319322
}
323+
324+
// Compute its chunking and store its information in the Entry's m_chunk_feerate.
325+
LinearizationChunking chunking(m_depgraph, m_linearization);
326+
LinearizationIndex lin_idx{0};
327+
// Iterate over the chunks.
328+
for (unsigned chunk_idx = 0; chunk_idx < chunking.NumChunksLeft(); ++chunk_idx) {
329+
auto chunk = chunking.GetChunk(chunk_idx);
330+
Assume(chunk.transactions.Any());
331+
// Iterate over the transactions in the linearization, which must match those in chunk.
332+
do {
333+
DepGraphIndex idx = m_linearization[lin_idx++];
334+
GraphIndex graph_idx = m_mapping[idx];
335+
auto& entry = graph.m_entries[graph_idx];
336+
entry.m_chunk_feerate = FeePerWeight::FromFeeFrac(chunk.feerate);
337+
Assume(chunk.transactions[idx]);
338+
chunk.transactions.Reset(idx);
339+
} while(chunk.transactions.Any());
340+
}
320341
}
321342

322343
void Cluster::ApplyRemovals(TxGraphImpl& graph, std::span<GraphIndex>& to_remove) noexcept
@@ -1108,6 +1129,23 @@ FeePerWeight TxGraphImpl::GetIndividualFeerate(const Ref& arg) noexcept
11081129
return cluster->GetIndividualFeerate(m_entries[GetRefIndex(arg)].m_locator.index);
11091130
}
11101131

1132+
FeePerWeight TxGraphImpl::GetChunkFeerate(const Ref& arg) noexcept
1133+
{
1134+
// Return the empty FeePerWeight if the passed Ref is empty.
1135+
if (GetRefGraph(arg) == nullptr) return {};
1136+
Assume(GetRefGraph(arg) == this);
1137+
// Apply all removals and dependencies, as the result might be inaccurate otherwise.
1138+
ApplyDependencies();
1139+
// Find the cluster the argument is in, and return the empty FeePerWeight if it isn't in any.
1140+
auto cluster = m_entries[GetRefIndex(arg)].m_locator.cluster;
1141+
if (cluster == nullptr) return {};
1142+
// Make sure the Cluster has an acceptable quality level, and then return the transaction's
1143+
// chunk feerate.
1144+
MakeAcceptable(*cluster);
1145+
const auto& entry = m_entries[GetRefIndex(arg)];
1146+
return entry.m_chunk_feerate;
1147+
}
1148+
11111149
void Cluster::SetFee(TxGraphImpl& graph, DepGraphIndex idx, int64_t fee) noexcept
11121150
{
11131151
// Make sure the specified DepGraphIndex exists in this Cluster.
@@ -1159,10 +1197,11 @@ void Cluster::SanityCheck(const TxGraphImpl& graph) const
11591197
// Check that the Entry has a locator pointing back to this Cluster & position within it.
11601198
assert(entry.m_locator.cluster == this);
11611199
assert(entry.m_locator.index == lin_pos);
1162-
// Check linearization position.
1200+
// Check linearization position and chunk feerate.
11631201
if (!linchunking.GetChunk(0).transactions[lin_pos]) {
11641202
linchunking.MarkDone(linchunking.GetChunk(0).transactions);
11651203
}
1204+
assert(entry.m_chunk_feerate == linchunking.GetChunk(0).feerate);
11661205
// If this Cluster has an acceptable quality level, its chunks must be connected.
11671206
if (IsAcceptable()) {
11681207
assert(m_depgraph.IsConnected(linchunking.GetChunk(0).transactions));

src/txgraph.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ static constexpr unsigned CLUSTER_COUNT_LIMIT{64};
2929
*
3030
* For more explanation, see https://delvingbitcoin.org/t/introduction-to-cluster-linearization/1032
3131
*
32+
* This linearization is partitioned into chunks: groups of transactions that according to this
33+
* order would be mined together. Each chunk consists of the highest-feerate prefix of what remains
34+
* of the linearization after removing previous chunks. TxGraph guarantees that the maintained
35+
* linearization always results in chunks consisting of transactions that are connected. A chunk's
36+
* transactions always belong to the same cluster.
37+
*
3238
* The interface is designed to accommodate an implementation that only stores the transitive
3339
* closure of dependencies, so if B spends C, it does not distinguish between "A spending B" and
3440
* "A spending both B and C".
@@ -82,6 +88,9 @@ class TxGraph
8288
/** Get the individual transaction feerate of transaction arg. Returns the empty FeePerWeight
8389
* if arg does not exist. */
8490
virtual FeePerWeight GetIndividualFeerate(const Ref& arg) noexcept = 0;
91+
/** Get the feerate of the chunk which transaction arg is in. Returns the empty FeePerWeight if
92+
* arg does not exist. */
93+
virtual FeePerWeight GetChunkFeerate(const Ref& arg) noexcept = 0;
8594
/** Get pointers to all transactions in the cluster which arg is in. The transactions will be
8695
* returned in graph order. Returns {} if arg does not exist in the graph. */
8796
virtual std::vector<Ref*> GetCluster(const Ref& arg) noexcept = 0;

0 commit comments

Comments
 (0)