v31.1-roots.1 · Commit 3: [PATCH 03/17] feat(policy): preserve cluster accounting and repair dynamic dust
src/test/fuzz/tx_pool.cpp
Before: src/test/fuzz/tx_pool.cpp · After: src/test/fuzz/tx_pool.cpp
File metadata
index bb15552723afa9485549d4379543bee4f6eca3b4..b0589bd50f00cfc959cb9cc15d08fbcaf1fa37e7 100644
Download original patch · Release notes
Diff rows 1–145 of 145. 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/fuzz/tx_pool.cpp
CHANGED
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
#include <validation.h>
|
|
22
22
|
#include <validationinterface.h>
|
|
23
23
|
|
|
24
|
+
#include <boost/multiprecision/cpp_int.hpp>
|
|
25
|
+
|
|
26
|
+
#include <algorithm>
|
|
27
|
+
#include <array>
|
|
28
|
+
|
|
24
29
|
using node::BlockAssembler;
|
|
25
30
|
using node::NodeContext;
|
|
26
31
|
using util::ToString;
|
|
@@ -463,4 +468,134 @@ FUZZ_TARGET(tx_pool, .init = initialize_tx_pool)
|
|
|
463
468
|
}
|
|
464
469
|
Finish(fuzzed_data_provider, tx_pool, chainstate);
|
|
465
470
|
}
|
|
471
|
+
FUZZ_TARGET(subdust_fee_penalty, .init = initialize_tx_pool)
|
|
472
|
+
{
|
|
473
|
+
SeedRandomStateForTest(SeedRand::ZEROS);
|
|
474
|
+
FuzzedDataProvider provider{buffer.data(), buffer.size()};
|
|
475
|
+
const auto& node{g_setup->m_node};
|
|
476
|
+
auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};
|
|
477
|
+
SetMockTime(chainstate.m_chain.Tip()->GetMedianTimePast() + 1);
|
|
478
|
+
|
|
479
|
+
using Wide = boost::multiprecision::int128_t;
|
|
480
|
+
const auto clamp_amount = [](Wide amount) -> CAmount {
|
|
481
|
+
return std::clamp(amount, Wide{INT64_MIN}, Wide{INT64_MAX}).convert_to<CAmount>();
|
|
482
|
+
};
|
|
483
|
+
// All these outputs are below 1,000 bytes including the spend estimate.
|
|
484
|
+
// Thus an arbitrary nonnegative int64 rate has a representable threshold.
|
|
485
|
+
// Larger scripts/rates require a separate threshold-overflow decision.
|
|
486
|
+
const std::array<CScript, 5> scripts{
|
|
487
|
+
CScript{} << OP_1 << std::vector<unsigned char>(32, 1),
|
|
488
|
+
CScript{} << OP_0 << std::vector<unsigned char>(20, 1),
|
|
489
|
+
CScript{} << OP_1 << std::vector<unsigned char>{0x4e, 0x73},
|
|
490
|
+
CScript{} << OP_DUP << OP_HASH160 << std::vector<unsigned char>(20, 1) << OP_EQUALVERIFY << OP_CHECKSIG,
|
|
491
|
+
CScript{} << OP_RETURN << std::vector<unsigned char>(16, 1),
|
|
492
|
+
};
|
|
493
|
+
const CAmount rate{provider.ConsumeIntegralInRange<CAmount>(0, INT64_MAX)};
|
|
494
|
+
const CAmount fee{provider.ConsumeIntegralInRange<CAmount>(0, 1'000'000)};
|
|
495
|
+
const CAmount delta{provider.ConsumeIntegralInRange<CAmount>(-MAX_MONEY, MAX_MONEY)};
|
|
496
|
+
CMutableTransaction mutable_tx;
|
|
497
|
+
mutable_tx.vin.emplace_back(g_outpoints_coinbase_init_mature.front());
|
|
498
|
+
mutable_tx.vin[0].scriptWitness.stack = {WITNESS_STACK_ELEM_OP_TRUE};
|
|
499
|
+
CAmount output_value{0};
|
|
500
|
+
const auto output_count{provider.ConsumeIntegralInRange<unsigned>(1, 16)};
|
|
501
|
+
for (unsigned i{0}; i < output_count; ++i) {
|
|
502
|
+
const CAmount value{provider.ConsumeIntegralInRange<CAmount>(0, 1000)};
|
|
503
|
+
mutable_tx.vout.emplace_back(value, provider.PickValueInArray(scripts));
|
|
504
|
+
output_value += value;
|
|
505
|
+
}
|
|
506
|
+
mutable_tx.vout.emplace_back(50 * COIN - fee - output_value, P2WSH_OP_TRUE);
|
|
507
|
+
const auto tx{MakeTransactionRef(mutable_tx)};
|
|
508
|
+
|
|
509
|
+
Wide deficit{0};
|
|
510
|
+
for (const auto& output : tx->vout) {
|
|
511
|
+
CAmount threshold{0};
|
|
512
|
+
if (!output.scriptPubKey.IsUnspendable()) {
|
|
513
|
+
int witness_version;
|
|
514
|
+
std::vector<unsigned char> program;
|
|
515
|
+
const auto bytes{GetSerializeSize(output) + (output.scriptPubKey.IsWitnessProgram(witness_version, program) ? 67 : 148)};
|
|
516
|
+
threshold = ((Wide{rate} * bytes + 999) / 1000).convert_to<CAmount>();
|
|
517
|
+
}
|
|
518
|
+
Assert(GetDustThreshold(output, CFeeRate{rate}) == threshold);
|
|
519
|
+
deficit += std::max<CAmount>(0, threshold - output.nValue);
|
|
520
|
+
}
|
|
521
|
+
const CAmount penalty{clamp_amount(deficit)};
|
|
522
|
+
const auto previous_mempool{chainstate.GetMempool()};
|
|
523
|
+
for (const bool enabled : {false, true}) {
|
|
524
|
+
auto options{MemPoolOptionsForTest(node)};
|
|
525
|
+
options.require_standard = false;
|
|
526
|
+
options.datacarrier_fullcount = false;
|
|
527
|
+
options.accept_non_std_datacarrier = true;
|
|
528
|
+
options.subdustfeepenalty = enabled;
|
|
529
|
+
options.dust_relay_feerate = CFeeRate{rate};
|
|
530
|
+
options.min_relay_feerate = CFeeRate{1000};
|
|
531
|
+
options.check_ratio = 0;
|
|
532
|
+
bilingual_str error;
|
|
533
|
+
CTxMemPool pool{options, error};
|
|
534
|
+
Assert(error.empty());
|
|
535
|
+
chainstate.SetMempool(&pool);
|
|
536
|
+
pool.PrioritiseTransaction(tx->GetHash(), delta);
|
|
537
|
+
const CAmount modified{clamp_amount(Wide{fee} + delta - (enabled ? penalty : 0))};
|
|
538
|
+
const auto result{WITH_LOCK(cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), /*bypass_limits=*/false, /*test_accept=*/false))};
|
|
539
|
+
// These valid v2 fixtures have only the relay floor as an admission gate.
|
|
540
|
+
Assert((result.m_result_type == MempoolAcceptResult::ResultType::VALID) == (modified >= GetVirtualTransactionSize(*tx)));
|
|
541
|
+
if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
|
|
542
|
+
LOCK(pool.cs);
|
|
543
|
+
const auto entry{Assert(pool.GetEntry(tx->GetHash()))};
|
|
544
|
+
Assert(entry->GetFee() == fee);
|
|
545
|
+
Assert(entry->GetModifiedFee() == modified);
|
|
546
|
+
Assert(pool.GetMainChunkFeerate(*entry).fee == modified);
|
|
547
|
+
Assert(pool.CalculateAncestorData(*entry) == std::make_tuple(size_t{1}, static_cast<size_t>(entry->GetTxSize()), modified));
|
|
548
|
+
} else {
|
|
549
|
+
Assert(result.m_state.GetRejectReason() == "min relay fee not met");
|
|
550
|
+
}
|
|
551
|
+
node.validation_signals->SyncWithValidationInterfaceQueue();
|
|
552
|
+
chainstate.SetMempool(previous_mempool);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
FUZZ_TARGET(dynamic_dust_fee)
|
|
556
|
+
{
|
|
557
|
+
FuzzedDataProvider provider{buffer.data(), buffer.size()};
|
|
558
|
+
const CAmount fee{provider.ConsumeIntegral<CAmount>()};
|
|
559
|
+
const int32_t size{provider.ConsumeIntegralInRange<int32_t>(1, INT32_MAX)};
|
|
560
|
+
const int multiplier{provider.ConsumeIntegralInRange<int>(1, INT32_MAX)};
|
|
561
|
+
using Wide = boost::multiprecision::int128_t;
|
|
562
|
+
const Wide maximum{std::numeric_limits<CAmount>::max()};
|
|
563
|
+
const Wide minimum{std::numeric_limits<CAmount>::min()};
|
|
564
|
+
const Wide product{Wide{fee} * 1000};
|
|
565
|
+
Wide per_kvb{product / size};
|
|
566
|
+
if (product < 0 && product % size != 0) --per_kvb;
|
|
567
|
+
per_kvb = std::max(minimum, std::min(maximum, per_kvb));
|
|
568
|
+
const CFeeRate estimate{fee, size};
|
|
569
|
+
Assert(estimate.GetFeePerK() == static_cast<CAmount>(per_kvb));
|
|
570
|
+
const Wide scaled{std::max(Wide{0}, per_kvb) * multiplier / 1000};
|
|
571
|
+
Assert(ScaleDustFee(estimate, multiplier).GetFeePerK() == static_cast<CAmount>(std::min(maximum, scaled)));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
FUZZ_TARGET(carrier_policy_weight)
|
|
575
|
+
{
|
|
576
|
+
SeedRandomStateForTest(SeedRand::ZEROS);
|
|
577
|
+
FuzzedDataProvider provider{buffer.data(), buffer.size()};
|
|
578
|
+
const unsigned int cost{provider.ConsumeIntegral<unsigned int>()};
|
|
579
|
+
CMutableTransaction tx;
|
|
580
|
+
tx.vin.resize(1);
|
|
581
|
+
uint64_t data_bytes{0};
|
|
582
|
+
const auto outputs{provider.ConsumeIntegralInRange<unsigned>(1, 16)};
|
|
583
|
+
for (unsigned i{0}; i < outputs; ++i) {
|
|
584
|
+
const auto bytes{provider.ConsumeBytes<unsigned char>(provider.ConsumeIntegralInRange<unsigned>(0, 4096))};
|
|
585
|
+
const CScript script{CScript{} << OP_RETURN << bytes};
|
|
586
|
+
data_bytes += script.size();
|
|
587
|
+
tx.vout.emplace_back(0, script);
|
|
588
|
+
}
|
|
589
|
+
CCoinsView view;
|
|
590
|
+
CCoinsViewCache cache{&view};
|
|
591
|
+
const auto ref{MakeTransactionRef(tx)};
|
|
592
|
+
const uint64_t extra{cost > WITNESS_SCALE_FACTOR ? data_bytes * (cost - WITNESS_SCALE_FACTOR) : 0};
|
|
593
|
+
const auto expected{static_cast<int32_t>(std::min<uint64_t>(extra, INT32_MAX))};
|
|
594
|
+
Assert(CalculateExtraTxWeight(*ref, cache, cost) == expected);
|
|
595
|
+
const CTxMemPoolEntry entry{ref, 123, 0, 1, 0, false, 0, {}, expected};
|
|
596
|
+
Assert(entry.GetFee() == 123);
|
|
597
|
+
Assert(entry.GetTxWeight() == GetTransactionWeight(*ref));
|
|
598
|
+
Assert(entry.GetAdjustedWeight() == GetTransactionWeight(*ref) + int64_t{expected});
|
|
599
|
+
Assert(entry.GetTxSize() == (entry.GetAdjustedWeight() + 3) / 4);
|
|
600
|
+
}
|
|
466
601
|
} // namespace
|