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. Before is on the left; after is on the right. Unified view fits narrow screens more easily.
src/test/fuzz/tx_pool.cpp
CHANGED
|
@@ -21,6 +21,11 @@
|
|
| 21 |
#include <validation.h>
|
| 22 |
#include <validationinterface.h>
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
using node::BlockAssembler;
|
| 25 |
using node::NodeContext;
|
| 26 |
using util::ToString;
|
|
@@ -463,4 +468,134 @@ FUZZ_TARGET(tx_pool, .init = initialize_tx_pool)
|
|
| 463 |
}
|
| 464 |
Finish(fuzzed_data_provider, tx_pool, chainstate);
|
| 465 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 466 |
} // namespace
|
|
|
|
| 21 |
#include <validation.h>
|
| 22 |
#include <validationinterface.h>
|
| 23 |
|
| 24 |
+
#include <boost/multiprecision/cpp_int.hpp>
|
| 25 |
+
|
| 26 |
+
#include <algorithm>
|
| 27 |
+
#include <array>
|
| 28 |
+
|
| 29 |
using node::BlockAssembler;
|
| 30 |
using node::NodeContext;
|
| 31 |
using util::ToString;
|
|
|
|
| 468 |
}
|
| 469 |
Finish(fuzzed_data_provider, tx_pool, chainstate);
|
| 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 |
+
}
|
| 601 |
} // namespace
|