6
6
#include < node/context.h>
7
7
#include < node/mempool_args.h>
8
8
#include < node/miner.h>
9
+ #include < policy/v3_policy.h>
9
10
#include < test/fuzz/FuzzedDataProvider.h>
10
11
#include < test/fuzz/fuzz.h>
11
12
#include < test/fuzz/util.h>
@@ -119,7 +120,8 @@ CTxMemPool MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeConte
119
120
mempool_opts.limits .descendant_size_vbytes = fuzzed_data_provider.ConsumeIntegralInRange <unsigned >(0 , 202 ) * 1'000 ;
120
121
mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange <unsigned >(0 , 200 ) * 1'000'000 ;
121
122
mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange <unsigned >(0 , 999 )};
122
- nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange <unsigned >(1 , 999 );
123
+ // Only interested in 2 cases: sigop cost 0 or when single legacy sigop cost is >> 1KvB
124
+ nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange <unsigned >(0 , 1 ) * 10'000 ;
123
125
124
126
mempool_opts.check_ratio = 1 ;
125
127
mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool ();
@@ -171,11 +173,11 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
171
173
// Create transaction to add to the mempool
172
174
const CTransactionRef tx = [&] {
173
175
CMutableTransaction tx_mut;
174
- tx_mut.nVersion = CTransaction::CURRENT_VERSION;
176
+ tx_mut.nVersion = fuzzed_data_provider. ConsumeBool () ? 3 : CTransaction::CURRENT_VERSION;
175
177
tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool () ? 0 : fuzzed_data_provider.ConsumeIntegral <uint32_t >();
176
178
// Last tx will sweep all outpoints in package
177
179
const auto num_in = last_tx ? package_outpoints.size () : fuzzed_data_provider.ConsumeIntegralInRange <int >(1 , mempool_outpoints.size ());
178
- const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange <int >(1 , mempool_outpoints.size () * 2 );
180
+ auto num_out = fuzzed_data_provider.ConsumeIntegralInRange <int >(1 , mempool_outpoints.size () * 2 );
179
181
180
182
auto & outpoints = last_tx ? package_outpoints : mempool_outpoints;
181
183
@@ -211,17 +213,24 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
211
213
tx_mut.vin .push_back (tx_mut.vin .back ());
212
214
}
213
215
214
- // Refer to a non-existant input
216
+ // Refer to a non-existent input
215
217
if (fuzzed_data_provider.ConsumeBool ()) {
216
218
tx_mut.vin .emplace_back ();
217
219
}
218
220
221
+ // Make a p2pk output to make sigops adjusted vsize to violate v3, potentially, which is never spent
222
+ if (last_tx && amount_in > 1000 && fuzzed_data_provider.ConsumeBool ()) {
223
+ tx_mut.vout .emplace_back (1000 , CScript () << std::vector<unsigned char >(33 , 0x02 ) << OP_CHECKSIG);
224
+ // Don't add any other outputs.
225
+ num_out = 1 ;
226
+ amount_in -= 1000 ;
227
+ }
228
+
219
229
const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange <CAmount>(0 , amount_in);
220
230
const auto amount_out = (amount_in - amount_fee) / num_out;
221
231
for (int i = 0 ; i < num_out; ++i) {
222
232
tx_mut.vout .emplace_back (amount_out, P2WSH_EMPTY);
223
233
}
224
- // TODO vary transaction sizes to catch size-related issues
225
234
auto tx = MakeTransactionRef (tx_mut);
226
235
// Restore previously removed outpoints, except in-package outpoints
227
236
if (!last_tx) {
@@ -261,7 +270,6 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
261
270
std::set<CTransactionRef> added;
262
271
auto txr = std::make_shared<TransactionsDelta>(added);
263
272
RegisterSharedValidationInterface (txr);
264
- const bool bypass_limits = fuzzed_data_provider.ConsumeBool ();
265
273
266
274
// When there are multiple transactions in the package, we call ProcessNewPackage(txs, test_accept=false)
267
275
// and AcceptToMemoryPool(txs.back(), test_accept=true). When there is only 1 transaction, we might flip it
@@ -271,17 +279,20 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
271
279
const auto result_package = WITH_LOCK (::cs_main,
272
280
return ProcessNewPackage (chainstate, tx_pool, txs, /* test_accept=*/ single_submit));
273
281
274
- const auto res = WITH_LOCK (::cs_main, return AcceptToMemoryPool (chainstate, txs.back (), GetTime (), bypass_limits, /* test_accept=*/ !single_submit));
275
- const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
282
+ // Always set bypass_limits to false because it is not supported in ProcessNewPackage and
283
+ // can be a source of divergence.
284
+ const auto res = WITH_LOCK (::cs_main, return AcceptToMemoryPool (chainstate, txs.back (), GetTime (),
285
+ /* bypass_limits=*/ false , /* test_accept=*/ !single_submit));
286
+ const bool passed = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
276
287
277
288
SyncWithValidationInterfaceQueue ();
278
289
UnregisterSharedValidationInterface (txr);
279
290
280
291
// There is only 1 transaction in the package. We did a test-package-accept and a ATMP
281
292
if (single_submit) {
282
- Assert (accepted != added.empty ());
283
- Assert (accepted == res.m_state .IsValid ());
284
- if (accepted ) {
293
+ Assert (passed != added.empty ());
294
+ Assert (passed == res.m_state .IsValid ());
295
+ if (passed ) {
285
296
Assert (added.size () == 1 );
286
297
Assert (txs.back () == *added.begin ());
287
298
}
@@ -295,6 +306,8 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
295
306
// This is empty if it fails early checks, or "full" if transactions are looked at deeper
296
307
Assert (result_package.m_tx_results .size () == txs.size () || result_package.m_tx_results .empty ());
297
308
}
309
+
310
+ CheckMempoolV3Invariants (tx_pool);
298
311
}
299
312
300
313
UnregisterSharedValidationInterface (outpoints_updater);
0 commit comments