Skip to content
Bitcoin Roots

v31.1-roots.1 · Commit 3: [PATCH 03/17] feat(policy): preserve cluster accounting and repair dynamic dust

src/test/mempool_tests.cpp

Before: src/test/mempool_tests.cpp · After: src/test/mempool_tests.cpp

File metadata
index 67e2643837d03312c92ec837606e68d4eba07060..070e9b1c493e3c17fff363e6955b52b3818132bf 100644

Download original patch · Release notes

Diff rows 1–253 of 253. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.

src/test/mempool_tests.cpp CHANGED
@@ -11,12 +11,253 @@
11
11
  #include <test/util/setup_common.h>
12
12
 
13
13
  #include <boost/test/unit_test.hpp>
14
+ #include <limits>
14
15
  #include <vector>
16
+ #include <thread>
15
17
 
16
18
  BOOST_FIXTURE_TEST_SUITE(mempool_tests, TestingSetup)
17
19
 
18
20
  static constexpr auto REMOVAL_REASON_DUMMY = MemPoolRemovalReason::REPLACED;
19
21
 
22
+ BOOST_AUTO_TEST_CASE(MempoolExtraWeightBoundary)
23
+ {
24
+ CMutableTransaction tx;
25
+ tx.vin.resize(1);
26
+ tx.vout.emplace_back(0, CScript{} << OP_RETURN << std::vector<unsigned char>(100, 0));
27
+ const auto ref = MakeTransactionRef(tx);
28
+ CCoinsView view;
29
+ CCoinsViewCache cache{&view};
30
+ const int32_t maximum = CalculateExtraTxWeight(*ref, cache, std::numeric_limits<unsigned int>::max());
31
+ BOOST_REQUIRE_EQUAL(maximum, std::numeric_limits<int32_t>::max());
32
+ const auto weight = GetTransactionWeight(*ref);
33
+ for (const int32_t extra : {0, 4, maximum - int32_t{weight}, maximum}) {
34
+ const CTxMemPoolEntry entry{ref, 100, 0, 1, 0, false, 0, {}, extra};
35
+ BOOST_CHECK_EQUAL(entry.GetTxWeight(), weight);
36
+ BOOST_CHECK_EQUAL(entry.GetAdjustedWeight(), int64_t{weight} + extra);
37
+ BOOST_CHECK_EQUAL(entry.GetTxSize(), (int64_t{weight} + extra + 3) / 4);
38
+ }
39
+ }
40
+
41
+ BOOST_AUTO_TEST_CASE(DynamicDustPublication)
42
+ {
43
+ auto options = MemPoolOptionsForTest(m_node);
44
+ options.dust_relay_target = 1;
45
+ options.dust_relay_multiplier = 3000;
46
+ options.dust_relay_feerate = CFeeRate{3000};
47
+ bilingual_str error;
48
+ CTxMemPool pool{options, error};
49
+ BOOST_REQUIRE(error.empty());
50
+ BOOST_CHECK_EQUAL(pool.GetDustRelayFee().GetFeePerK(), 3000);
51
+ BOOST_CHECK(!pool.EstimateDustFromMempool(1));
52
+ CMutableTransaction mutable_tx;
53
+ mutable_tx.vin.resize(1);
54
+ mutable_tx.vout.emplace_back(10000, CScript{} << OP_TRUE);
55
+ const auto tx = MakeTransactionRef(mutable_tx);
56
+ {
57
+ LOCK2(cs_main, pool.cs);
58
+ auto stage = pool.GetChangeSet();
59
+ stage->StageAddition(tx, 30000, 0, 1, 0, false, 0, {}, 4000 - GetTransactionWeight(*tx), 123);
60
+ stage->Apply();
61
+ }
62
+ const auto estimate = pool.EstimateDustFromMempool(1);
63
+ BOOST_REQUIRE(estimate);
64
+ BOOST_CHECK_EQUAL(estimate->GetFeePerK(), 30000);
65
+ BOOST_CHECK(!pool.EstimateDustFromMempool(INT32_MAX));
66
+ pool.UpdateDynamicDustFee(estimate);
67
+ BOOST_CHECK_EQUAL(pool.GetStandardnessOptions().dust_relay_feerate.GetFeePerK(), 90000);
68
+ {
69
+ LOCK(pool.cs);
70
+ const auto entry = pool.GetEntry(tx->GetHash());
71
+ BOOST_CHECK_EQUAL(entry->GetModifiedFee(), 29877);
72
+ BOOST_CHECK_EQUAL(pool.GetMainChunkFeerate(*entry).fee, 29877);
73
+ BOOST_CHECK_EQUAL(pool.GetTotalFee(), 30000);
74
+ BOOST_REQUIRE(pool.PrioritiseTransaction(tx->GetHash(), 1'000'000));
75
+ }
76
+ BOOST_CHECK_EQUAL(pool.EstimateDustFromMempool(1)->GetFeePerK(), 30000);
77
+ pool.UpdateDynamicDustFee(std::nullopt);
78
+ BOOST_CHECK_EQUAL(pool.GetDustRelayFee().GetFeePerK(), 3000);
79
+ pool.UpdateDynamicDustFee(CFeeRate{0});
80
+ BOOST_CHECK_EQUAL(pool.GetDustRelayFee().GetFeePerK(), 3000);
81
+ std::atomic<bool> valid{true};
82
+ std::thread reader{[&] {
83
+ for (int i = 0; i < 10000; ++i) {
84
+ const auto fee = pool.GetStandardnessOptions().dust_relay_feerate.GetFeePerK();
85
+ if (fee != 3000 && fee != 90000) valid = false;
86
+ }
87
+ }};
88
+ for (int i = 0; i < 10000; ++i) pool.UpdateDynamicDustFee(i % 2 ? estimate : std::nullopt);
89
+ reader.join();
90
+ BOOST_CHECK(valid.load());
91
+ options.dust_relay_target = 0;
92
+ CTxMemPool disabled{options, error};
93
+ disabled.UpdateDynamicDustFee(CFeeRate{90000});
94
+ BOOST_CHECK_EQUAL(disabled.GetDustRelayFee().GetFeePerK(), 3000);
95
+ }
96
+
97
+ BOOST_AUTO_TEST_CASE(GraphPolicyAccounting)
98
+ {
99
+ auto options = MemPoolOptionsForTest(m_node);
100
+ bilingual_str error;
101
+ CTxMemPool pool{options, error};
102
+ Assert(error.empty());
103
+ LOCK2(cs_main, pool.cs);
104
+ CMutableTransaction parent;
105
+ parent.vin.resize(1);
106
+ parent.vout.emplace_back(10000, CScript{} << OP_TRUE);
107
+ const auto p = MakeTransactionRef(parent);
108
+ CMutableTransaction child;
109
+ child.vin.emplace_back(COutPoint{p->GetHash(), 0});
110
+ child.vout.emplace_back(9000, CScript{} << OP_TRUE);
111
+ const auto c = MakeTransactionRef(child);
112
+ pool.PrioritiseTransaction(p->GetHash(), 7);
113
+ {
114
+ auto stage = pool.GetChangeSet();
115
+ const auto entry = stage->StageAddition(p, 100, 0, 1, 0, false, 0, {}, 5, 130);
116
+ BOOST_CHECK_EQUAL(entry->GetFee(), 100);
117
+ BOOST_CHECK_EQUAL(entry->GetModifiedFee(), -23);
118
+ const auto fees = stage->CalculateChunksForRBF();
119
+ BOOST_REQUIRE(fees);
120
+ BOOST_CHECK_EQUAL(fees->second.back().fee, -23);
121
+ BOOST_CHECK_EQUAL(fees->second.back().size, GetTransactionWeight(*p) + 5);
122
+ stage->Apply();
123
+ }
124
+ {
125
+ auto stage = pool.GetChangeSet();
126
+ stage->StageAddition(c, 1000, 0, 1, 0, false, 0, {}, 3, 30);
127
+ stage->Apply();
128
+ }
129
+ const auto original_fee = pool.GetTotalFee();
130
+ const auto entry = pool.GetEntry(p->GetHash());
131
+ auto chunk = pool.GetMainChunkFeerate(*entry);
132
+ BOOST_CHECK_EQUAL(chunk.fee, 947);
133
+ BOOST_CHECK_EQUAL(chunk.size, GetTransactionWeight(*p) + GetTransactionWeight(*c) + 8);
134
+ pool.PrioritiseTransaction(p->GetHash(), 13);
135
+ BOOST_CHECK_EQUAL(pool.GetMainChunkFeerate(*entry).fee, 960);
136
+ BOOST_CHECK_EQUAL(pool.GetTotalFee(), original_fee);
137
+ {
138
+ auto stage = pool.GetChangeSet();
139
+ stage->StageRemoval(pool.GetIter(c->GetHash()).value());
140
+ stage->StageAddition(c, 2000, 0, 1, 0, false, 0, {}, 9, 40);
141
+ const auto fees = stage->CalculateChunksForRBF();
142
+ BOOST_REQUIRE(fees);
143
+ BOOST_CHECK_EQUAL(fees->first.back().fee, 960);
144
+ BOOST_CHECK_EQUAL(fees->second.back().fee, 1950);
145
+ // Destructor aborts: both entry accounting and main diagram survive.
146
+ }
147
+ BOOST_CHECK_EQUAL(pool.GetMainChunkFeerate(*entry).fee, 960);
148
+ // Block removal detaches a surviving child, then reorg insertion discovers
149
+ // the dependency again using the authoritative policy fee/weight values.
150
+ pool.removeForBlock({p}, 2);
151
+ const auto child_entry = pool.GetEntry(c->GetHash());
152
+ BOOST_REQUIRE(child_entry);
153
+ BOOST_CHECK_EQUAL(pool.GetMainChunkFeerate(*child_entry).fee, 970);
154
+ BOOST_CHECK_EQUAL(pool.GetTotalFee(), 1000);
155
+ {
156
+ auto stage = pool.GetChangeSet();
157
+ stage->StageAddition(p, 100, 0, 1, 0, false, 0, {}, 5, 130);
158
+ stage->Apply();
159
+ }
160
+ pool.UpdateTransactionsFromBlock({p->GetHash()});
161
+ const auto reorg_entry = pool.GetEntry(p->GetHash());
162
+ BOOST_REQUIRE(reorg_entry);
163
+ BOOST_CHECK_EQUAL(pool.GetMainChunkFeerate(*reorg_entry).fee, 940);
164
+ BOOST_CHECK_EQUAL(pool.GetMainChunkFeerate(*reorg_entry).size, GetTransactionWeight(*p) + GetTransactionWeight(*c) + 8);
165
+ pool.removeRecursive(*c, REMOVAL_REASON_DUMMY);
166
+ BOOST_CHECK_EQUAL(pool.GetMainChunkFeerate(*reorg_entry).fee, -30);
167
+ pool.removeRecursive(*p, REMOVAL_REASON_DUMMY);
168
+ BOOST_CHECK_EQUAL(pool.GetTotalFee(), 0);
169
+ }
170
+
171
+ BOOST_AUTO_TEST_CASE(GraphPolicyBudgetsAndReuseClaims)
172
+ {
173
+ auto options = MemPoolOptionsForTest(m_node);
174
+ bilingual_str error;
175
+ CTxMemPool pool{options, error};
176
+ BOOST_REQUIRE(error.empty());
177
+ LOCK2(cs_main, pool.cs);
178
+ CMutableTransaction mutable_tx;
179
+ mutable_tx.vin.resize(1);
180
+ mutable_tx.vout.emplace_back(0, CScript{} << OP_TRUE);
181
+ const auto first = MakeTransactionRef(mutable_tx);
182
+ mutable_tx.nLockTime = 1;
183
+ const auto second = MakeTransactionRef(mutable_tx);
184
+ const CScript script{CScript{} << OP_2};
185
+ constexpr CAmount maximum{std::numeric_limits<CAmount>::max()};
186
+ BOOST_CHECK(!pool.CanAddModifiedFee(std::numeric_limits<CAmount>::min()));
187
+ BOOST_CHECK(pool.CanAddModifiedFee(maximum));
188
+ {
189
+ auto stage = pool.GetChangeSet();
190
+ stage->StageAddition(first, 0, 0, 1, 0, false, 0, {}, 0, maximum, {{script, 3}});
191
+ BOOST_CHECK(!pool.CanAddModifiedFee(1));
192
+ BOOST_CHECK(stage->HasStagedScriptConflict(script, true));
193
+ BOOST_CHECK(stage->HasStagedScriptConflict(script, false));
194
+ }
195
+ BOOST_CHECK(pool.CanAddModifiedFee(maximum));
196
+ {
197
+ auto stage = pool.GetChangeSet();
198
+ stage->StageAddition(first, 100, 0, 1, 0, false, 0, {}, 0, 10, {{script, 3}});
199
+ stage->StageAddition(second, 100, 0, 1, 0, false, 0, {}, 0, 10, {{script, 3}});
200
+ stage->Apply();
201
+ }
202
+ BOOST_CHECK_EQUAL(pool.GetScriptConflicts(script, true).size(), 2);
203
+ BOOST_CHECK(!pool.PrioritiseTransaction(first->GetHash(), maximum));
204
+ BOOST_CHECK_EQUAL(pool.GetEntry(first->GetHash())->GetModifiedFee(), 90);
205
+ pool.removeRecursive(*first, REMOVAL_REASON_DUMMY);
206
+ BOOST_CHECK(pool.GetScriptConflicts(script, true) == std::set<Txid>{second->GetHash()});
207
+ BOOST_CHECK(pool.PrioritiseTransaction(second->GetHash(), maximum));
208
+ BOOST_CHECK(!pool.CanAddModifiedFee(1));
209
+ pool.removeRecursive(*second, REMOVAL_REASON_DUMMY);
210
+ BOOST_CHECK(pool.GetScriptConflicts(script, true).empty());
211
+ BOOST_CHECK(pool.CanAddModifiedFee(maximum));
212
+ // The global absolute budget deliberately rejects some representable signed
213
+ // totals and independent singleton clusters; per-cluster limits cannot bound
214
+ // a replacement diagram spanning several clusters.
215
+ {
216
+ auto stage = pool.GetChangeSet();
217
+ stage->StageAddition(first, 0, 0, 1, 0, false, 0, {}, 0, maximum / 2);
218
+ stage->Apply();
219
+ }
220
+ BOOST_CHECK(pool.PrioritiseTransaction(first->GetHash(), maximum));
221
+ BOOST_CHECK_EQUAL(pool.GetEntry(first->GetHash())->GetModifiedFee(), maximum - maximum / 2);
222
+ BOOST_CHECK(!pool.CanAddModifiedFee(-(maximum / 2 + 1)));
223
+ BOOST_CHECK(!pool.CanAddModifiedFee(maximum / 2 + 1));
224
+ {
225
+ auto stage = pool.GetChangeSet();
226
+ stage->StageRemoval(pool.mapTx.find(first->GetHash()));
227
+ BOOST_CHECK(!pool.CanAddModifiedFee(maximum)); // Removal reserved until commit.
228
+ }
229
+ pool.removeRecursive(*first, REMOVAL_REASON_DUMMY);
230
+ BOOST_CHECK(pool.CanAddModifiedFee(maximum));
231
+ BOOST_CHECK(pool.CanAddPolicyWeight(std::numeric_limits<int32_t>::max()));
232
+ BOOST_CHECK(!pool.CanAddPolicyWeight(int64_t{std::numeric_limits<int32_t>::max()} + 1));
233
+ }
234
+
235
+ BOOST_AUTO_TEST_CASE(GraphPolicyTrimUsesModifiedFees)
236
+ {
237
+ auto options = MemPoolOptionsForTest(m_node);
238
+ bilingual_str error;
239
+ CTxMemPool pool{options, error};
240
+ BOOST_REQUIRE(error.empty());
241
+ LOCK2(cs_main, pool.cs);
242
+ CMutableTransaction tx;
243
+ tx.vin.resize(1);
244
+ tx.vout.emplace_back(0, CScript{} << OP_TRUE);
245
+ const auto penalized = MakeTransactionRef(tx);
246
+ tx.nLockTime = 1;
247
+ const auto unpenalized = MakeTransactionRef(tx);
248
+ {
249
+ auto stage = pool.GetChangeSet();
250
+ stage->StageAddition(penalized, 10000, 0, 1, 0, false, 0, {}, 0, 9990);
251
+ stage->StageAddition(unpenalized, 100, 0, 1, 0, false, 0, {});
252
+ stage->Apply();
253
+ }
254
+ pool.TrimToSize(pool.DynamicMemoryUsage() - 1);
255
+ BOOST_CHECK(!pool.exists(penalized->GetHash()));
256
+ BOOST_CHECK(pool.exists(unpenalized->GetHash()));
257
+ BOOST_CHECK_EQUAL(pool.GetTotalFee(), 100);
258
+ BOOST_CHECK_EQUAL(pool.GetEntry(unpenalized->GetHash())->GetModifiedFee(), 100);
259
+ }
260
+
20
261
  class MemPoolTest final : public CTxMemPool
21
262
  {
22
263
  public: