Skip to content

Commit 58f7e01

Browse files
committed
tests: framework for testing DepGraph class
This introduces a bespoke fuzzing-focused serialization format for DepGraphs, and then tests that this format can represent any graph, roundtrips, and then uses that to test the correctness of DepGraph itself. This forms the basis for future fuzz tests that need to work with interesting graphs.
1 parent a6e07e7 commit 58f7e01

File tree

5 files changed

+564
-0
lines changed

5 files changed

+564
-0
lines changed

src/Makefile.test.include

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ BITCOIN_TESTS =\
8383
test/bloom_tests.cpp \
8484
test/bswap_tests.cpp \
8585
test/checkqueue_tests.cpp \
86+
test/cluster_linearize_tests.cpp \
8687
test/coins_tests.cpp \
8788
test/coinstatsindex_tests.cpp \
8889
test/common_url_tests.cpp \
@@ -302,6 +303,7 @@ test_fuzz_fuzz_SOURCES = \
302303
test/fuzz/buffered_file.cpp \
303304
test/fuzz/chain.cpp \
304305
test/fuzz/checkqueue.cpp \
306+
test/fuzz/cluster_linearize.cpp \
305307
test/fuzz/coins_view.cpp \
306308
test/fuzz/coinscache_sim.cpp \
307309
test/fuzz/connman.cpp \

src/Makefile.test_util.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ EXTRA_LIBRARIES += \
1010
TEST_UTIL_H = \
1111
test/util/blockfilter.h \
1212
test/util/chainstate.h \
13+
test/util/cluster_linearize.h \
1314
test/util/coins.h \
1415
test/util/index.h \
1516
test/util/json.h \

src/test/cluster_linearize_tests.cpp

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright (c) The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <cluster_linearize.h>
6+
#include <test/util/cluster_linearize.h>
7+
#include <test/util/setup_common.h>
8+
#include <util/bitset.h>
9+
#include <util/strencodings.h>
10+
11+
#include <vector>
12+
13+
#include <boost/test/unit_test.hpp>
14+
15+
BOOST_FIXTURE_TEST_SUITE(cluster_linearize_tests, BasicTestingSetup)
16+
17+
using namespace cluster_linearize;
18+
19+
namespace {
20+
21+
template<typename SetType>
22+
void TestDepGraphSerialization(const Cluster<SetType>& cluster, const std::string& hexenc)
23+
{
24+
DepGraph depgraph(cluster);
25+
26+
// Run normal sanity and correspondence checks, which includes a round-trip test.
27+
VerifyDepGraphFromCluster(cluster, depgraph);
28+
29+
// There may be multiple serializations of the same graph, but DepGraphFormatter's serializer
30+
// only produces one of those. Verify that hexenc matches that canonical serialization.
31+
std::vector<unsigned char> encoding;
32+
VectorWriter writer(encoding, 0);
33+
writer << Using<DepGraphFormatter>(depgraph);
34+
BOOST_CHECK_EQUAL(HexStr(encoding), hexenc);
35+
36+
// Test that deserializing that encoding yields depgraph. This is effectively already implied
37+
// by the round-trip test above (if depgraph is acyclic), but verify it explicitly again here.
38+
SpanReader reader(encoding);
39+
DepGraph<SetType> depgraph_read;
40+
reader >> Using<DepGraphFormatter>(depgraph_read);
41+
BOOST_CHECK(depgraph == depgraph_read);
42+
}
43+
44+
} // namespace
45+
46+
BOOST_AUTO_TEST_CASE(depgraph_ser_tests)
47+
{
48+
// Empty cluster.
49+
TestDepGraphSerialization<TestBitSet>(
50+
{},
51+
"00" /* end of graph */);
52+
53+
// Transactions: A(fee=0,size=1).
54+
TestDepGraphSerialization<TestBitSet>(
55+
{{{0, 1}, {}}},
56+
"01" /* A size */
57+
"00" /* A fee */
58+
"00" /* A insertion position (no skips): A */
59+
"00" /* end of graph */);
60+
61+
// Transactions: A(fee=42,size=11), B(fee=-13,size=7), B depends on A.
62+
TestDepGraphSerialization<TestBitSet>(
63+
{{{42, 11}, {}}, {{-13, 7}, {0}}},
64+
"0b" /* A size */
65+
"54" /* A fee */
66+
"00" /* A insertion position (no skips): A */
67+
"07" /* B size */
68+
"19" /* B fee */
69+
"00" /* B->A dependency (no skips) */
70+
"00" /* B insertion position (no skips): A,B */
71+
"00" /* end of graph */);
72+
73+
// Transactions: A(64,128), B(128,256), C(1,1), C depends on A and B.
74+
TestDepGraphSerialization<TestBitSet>(
75+
{{{64, 128}, {}}, {{128, 256}, {}}, {{1, 1}, {0, 1}}},
76+
"8000" /* A size */
77+
"8000" /* A fee */
78+
"00" /* A insertion position (no skips): A */
79+
"8100" /* B size */
80+
"8100" /* B fee */
81+
"01" /* B insertion position (skip B->A dependency): A,B */
82+
"01" /* C size */
83+
"02" /* C fee */
84+
"00" /* C->B dependency (no skips) */
85+
"00" /* C->A dependency (no skips) */
86+
"00" /* C insertion position (no skips): A,B,C */
87+
"00" /* end of graph */);
88+
89+
// Transactions: A(-57,113), B(57,114), C(-58,115), D(58,116). Deps: B->A, C->A, D->C, in order
90+
// [B,A,C,D]. This exercises non-topological ordering (internally serialized as A,B,C,D).
91+
TestDepGraphSerialization<TestBitSet>(
92+
{{{57, 114}, {1}}, {{-57, 113}, {}}, {{-58, 115}, {1}}, {{58, 116}, {2}}},
93+
"71" /* A size */
94+
"71" /* A fee */
95+
"00" /* A insertion position (no skips): A */
96+
"72" /* B size */
97+
"72" /* B fee */
98+
"00" /* B->A dependency (no skips) */
99+
"01" /* B insertion position (skip A): B,A */
100+
"73" /* C size */
101+
"73" /* C fee */
102+
"01" /* C->A dependency (skip C->B dependency) */
103+
"00" /* C insertion position (no skips): B,A,C */
104+
"74" /* D size */
105+
"74" /* D fee */
106+
"00" /* D->C dependency (no skips) */
107+
"01" /* D insertion position (skip D->B dependency, D->A is implied): B,A,C,D */
108+
"00" /* end of graph */);
109+
110+
// Transactions: A(1,2), B(3,1), C(2,1), D(1,3), E(1,1). Deps: C->A, D->A, D->B, E->D.
111+
// In order: [D,A,B,E,C]. Internally serialized in order A,B,C,D,E.
112+
TestDepGraphSerialization<TestBitSet>(
113+
{{{1, 3}, {1, 2}}, {{1, 2}, {}}, {{3, 1}, {}}, {{1, 1}, {0}}, {{2, 1}, {1}}},
114+
"02" /* A size */
115+
"02" /* A fee */
116+
"00" /* A insertion position (no skips): A */
117+
"01" /* B size */
118+
"06" /* B fee */
119+
"01" /* B insertion position (skip B->A dependency): A,B */
120+
"01" /* C size */
121+
"04" /* C fee */
122+
"01" /* C->A dependency (skip C->B dependency) */
123+
"00" /* C insertion position (no skips): A,B,C */
124+
"03" /* D size */
125+
"02" /* D fee */
126+
"01" /* D->B dependency (skip D->C dependency) */
127+
"00" /* D->A dependency (no skips) */
128+
"03" /* D insertion position (skip C,B,A): D,A,B,C */
129+
"01" /* E size */
130+
"02" /* E fee */
131+
"00" /* E->D dependency (no skips) */
132+
"02" /* E insertion position (skip E->C dependency, E->B and E->A are implied,
133+
skip insertion C): D,A,B,E,C */
134+
"00" /* end of graph */
135+
);
136+
}
137+
138+
BOOST_AUTO_TEST_SUITE_END()

src/test/fuzz/cluster_linearize.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <cluster_linearize.h>
6+
#include <serialize.h>
7+
#include <streams.h>
8+
#include <test/fuzz/fuzz.h>
9+
#include <test/fuzz/FuzzedDataProvider.h>
10+
#include <test/util/cluster_linearize.h>
11+
#include <util/bitset.h>
12+
#include <util/feefrac.h>
13+
14+
#include <stdint.h>
15+
#include <vector>
16+
#include <utility>
17+
18+
FUZZ_TARGET(clusterlin_add_dependency)
19+
{
20+
// Verify that computing a DepGraph from a cluster, or building it step by step using AddDependency
21+
// have the same effect.
22+
23+
// Construct a cluster of a certain length, with no dependencies.
24+
FuzzedDataProvider provider(buffer.data(), buffer.size());
25+
auto num_tx = provider.ConsumeIntegralInRange<ClusterIndex>(2, 32);
26+
Cluster<TestBitSet> cluster(num_tx, std::pair{FeeFrac{0, 1}, TestBitSet{}});
27+
// Construct the corresponding DepGraph object (also no dependencies).
28+
DepGraph depgraph(cluster);
29+
SanityCheck(depgraph);
30+
// Read (parent, child) pairs, and add them to the cluster and depgraph.
31+
LIMITED_WHILE(provider.remaining_bytes() > 0, TestBitSet::Size() * TestBitSet::Size()) {
32+
auto parent = provider.ConsumeIntegralInRange<ClusterIndex>(0, num_tx - 1);
33+
auto child = provider.ConsumeIntegralInRange<ClusterIndex>(0, num_tx - 2);
34+
child += (child >= parent);
35+
cluster[child].second.Set(parent);
36+
depgraph.AddDependency(parent, child);
37+
assert(depgraph.Ancestors(child)[parent]);
38+
assert(depgraph.Descendants(parent)[child]);
39+
}
40+
// Sanity check the result.
41+
SanityCheck(depgraph);
42+
// Verify that the resulting DepGraph matches one recomputed from the cluster.
43+
assert(DepGraph(cluster) == depgraph);
44+
}
45+
46+
FUZZ_TARGET(clusterlin_cluster_serialization)
47+
{
48+
// Verify that any graph of transactions has its ancestry correctly computed by DepGraph, and
49+
// if it is a DAG, that it can be serialized as a DepGraph in a way that roundtrips. This
50+
// guarantees that any acyclic cluster has a corresponding DepGraph serialization.
51+
52+
FuzzedDataProvider provider(buffer.data(), buffer.size());
53+
54+
// Construct a cluster in a naive way (using a FuzzedDataProvider-based serialization).
55+
Cluster<TestBitSet> cluster;
56+
auto num_tx = provider.ConsumeIntegralInRange<ClusterIndex>(1, 32);
57+
cluster.resize(num_tx);
58+
for (ClusterIndex i = 0; i < num_tx; ++i) {
59+
cluster[i].first.size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
60+
cluster[i].first.fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
61+
for (ClusterIndex j = 0; j < num_tx; ++j) {
62+
if (i == j) continue;
63+
if (provider.ConsumeBool()) cluster[i].second.Set(j);
64+
}
65+
}
66+
67+
// Construct dependency graph, and verify it matches the cluster (which includes a round-trip
68+
// check for the serialization).
69+
DepGraph depgraph(cluster);
70+
VerifyDepGraphFromCluster(cluster, depgraph);
71+
}
72+
73+
FUZZ_TARGET(clusterlin_depgraph_serialization)
74+
{
75+
// Verify that any deserialized depgraph is acyclic and roundtrips to an identical depgraph.
76+
77+
// Construct a graph by deserializing.
78+
SpanReader reader(buffer);
79+
DepGraph<TestBitSet> depgraph;
80+
try {
81+
reader >> Using<DepGraphFormatter>(depgraph);
82+
} catch (const std::ios_base::failure&) {}
83+
SanityCheck(depgraph);
84+
85+
// Verify the graph is a DAG.
86+
assert(IsAcyclic(depgraph));
87+
}

0 commit comments

Comments
 (0)