2022-12-24 23:49:50 +00:00
|
|
|
// Copyright (c) 2020-2022 The Bitcoin Core developers
|
2020-04-26 19:29:03 +00:00
|
|
|
// Distributed under the MIT software license, see the accompanying
|
|
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
|
2025-12-05 09:31:39 +02:00
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
|
|
|
#include <config/bitcoin-config.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-08-02 15:30:00 +02:00
|
|
|
#include <node/mempool_args.h>
|
2020-04-26 19:29:03 +00:00
|
|
|
#include <policy/rbf.h>
|
|
|
|
|
#include <primitives/transaction.h>
|
|
|
|
|
#include <sync.h>
|
|
|
|
|
#include <test/fuzz/FuzzedDataProvider.h>
|
|
|
|
|
#include <test/fuzz/fuzz.h>
|
|
|
|
|
#include <test/fuzz/util.h>
|
2022-09-30 17:55:10 +01:00
|
|
|
#include <test/fuzz/util/mempool.h>
|
2022-06-24 12:30:14 -04:00
|
|
|
#include <test/util/setup_common.h>
|
2022-10-10 14:27:31 +02:00
|
|
|
#include <test/util/txmempool.h>
|
2020-04-26 19:29:03 +00:00
|
|
|
#include <txmempool.h>
|
2024-04-21 10:03:21 +02:00
|
|
|
#include <util/check.h>
|
|
|
|
|
#include <util/translation.h>
|
2020-04-26 19:29:03 +00:00
|
|
|
|
|
|
|
|
#include <cstdint>
|
2020-05-10 18:35:55 +00:00
|
|
|
#include <optional>
|
2020-04-26 19:29:03 +00:00
|
|
|
#include <string>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
2022-06-24 12:30:14 -04:00
|
|
|
namespace {
|
|
|
|
|
const BasicTestingSetup* g_setup;
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2024-01-12 11:05:05 -05:00
|
|
|
const int NUM_ITERS = 10000;
|
|
|
|
|
|
|
|
|
|
std::vector<COutPoint> g_outpoints;
|
|
|
|
|
|
2022-06-24 12:30:14 -04:00
|
|
|
void initialize_rbf()
|
|
|
|
|
{
|
|
|
|
|
static const auto testing_setup = MakeNoLogFileContext<>();
|
|
|
|
|
g_setup = testing_setup.get();
|
2025-01-26 16:51:10 +00:00
|
|
|
// ELEMENTS: our mempool needs Params() to be set for multiple reasons -- to check
|
|
|
|
|
// the discount CT rate, to figure out pegin policy, etc
|
2025-05-23 13:02:22 +02:00
|
|
|
SelectParams(ChainType::LIQUID1);
|
2022-06-24 12:30:14 -04:00
|
|
|
}
|
|
|
|
|
|
2024-01-12 11:05:05 -05:00
|
|
|
void initialize_package_rbf()
|
|
|
|
|
{
|
|
|
|
|
static const auto testing_setup = MakeNoLogFileContext<>();
|
|
|
|
|
g_setup = testing_setup.get();
|
|
|
|
|
|
|
|
|
|
// Create a fixed set of unique "UTXOs" to source parents from
|
|
|
|
|
// to avoid fuzzer giving circular references
|
|
|
|
|
for (int i = 0; i < NUM_ITERS; ++i) {
|
|
|
|
|
g_outpoints.emplace_back();
|
|
|
|
|
g_outpoints.back().n = i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-11 14:33:31 +02:00
|
|
|
FUZZ_TARGET(rbf, .init = initialize_rbf)
|
2020-04-26 19:29:03 +00:00
|
|
|
{
|
2024-12-13 14:22:55 +01:00
|
|
|
SeedRandomStateForTest(SeedRand::ZEROS);
|
2020-04-26 19:29:03 +00:00
|
|
|
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
|
2020-11-19 21:25:14 +00:00
|
|
|
SetMockTime(ConsumeTime(fuzzed_data_provider));
|
2023-09-07 19:16:57 +10:00
|
|
|
std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
|
2020-04-26 19:29:03 +00:00
|
|
|
if (!mtx) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
pool: Add and use MemPoolOptions, ApplyArgsManOptions
Reviewers: Note that CTxMemPool now requires a non-defaulted
CTxMemPool::Options for its constructor. Meaning that there's no need to
worry about a stray CTxMemPool constructor somewhere defaulting to
something incorrect. All instances of CTxMemPool construction are
addressed here in this commit.
We set options for CTxMemPool and construct it in many different ways. A
good example can be seen in how we determine CTxMemPool's check_ratio in
AppInitMain(...).
1. We first set the default based on chainparams's
DefaultConsistencyChecks()
2. Then, we apply the ArgsManager option on top of that default
3. Finally, we clamp the result of that between 0 and 1 Million
With this patch, most CTxMemPool construction are along the lines of:
MemPoolOptions mempool_opts{...default overrides...};
ApplyArgsManOptions(argsman, mempool_opts);
...hard overrides...
CTxMemPool pool{mempool_opts};
This "compositional" style of building options means that we can omit
unnecessary/irrelevant steps wherever we want but also maintain full
customizability.
For example:
- For users of libbitcoinkernel, where we eventually want to remove
ArgsManager, they simply won't call (or even know about)
ApplyArgsManOptions.
- See src/init.cpp to see how the check_ratio CTxMemPool option works
after this change.
A MemPoolOptionsForTest helper was also added and used by tests/fuzz
tests where a local CTxMemPool needed to be created.
The change in src/test/fuzz/tx_pool.cpp seemingly changes behaviour by
applying ArgsManager options on top of the CTxMemPool::Options defaults.
However, in future commits where we introduce flags like -maxmempool,
the call to ApplyArgsManOptions is actually what preserves the existing
behaviour. Previously, although it wasn't obvious, our CTxMemPool would
consult gArgs for flags like -maxmempool when it needed it, so it
already relied on ArgsManager information. This patchset just laid bare
the obfuscatory perils of globals.
[META] As this patchset progresses, we will move more and more
CTxMemPool-relevant options into MemPoolOptions and add their
ArgsMan-related logic to ApplyArgsManOptions.
2022-03-18 13:51:37 -04:00
|
|
|
|
2024-04-21 10:03:21 +02:00
|
|
|
bilingual_str error;
|
|
|
|
|
CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
|
|
|
|
|
Assert(error.empty());
|
pool: Add and use MemPoolOptions, ApplyArgsManOptions
Reviewers: Note that CTxMemPool now requires a non-defaulted
CTxMemPool::Options for its constructor. Meaning that there's no need to
worry about a stray CTxMemPool constructor somewhere defaulting to
something incorrect. All instances of CTxMemPool construction are
addressed here in this commit.
We set options for CTxMemPool and construct it in many different ways. A
good example can be seen in how we determine CTxMemPool's check_ratio in
AppInitMain(...).
1. We first set the default based on chainparams's
DefaultConsistencyChecks()
2. Then, we apply the ArgsManager option on top of that default
3. Finally, we clamp the result of that between 0 and 1 Million
With this patch, most CTxMemPool construction are along the lines of:
MemPoolOptions mempool_opts{...default overrides...};
ApplyArgsManOptions(argsman, mempool_opts);
...hard overrides...
CTxMemPool pool{mempool_opts};
This "compositional" style of building options means that we can omit
unnecessary/irrelevant steps wherever we want but also maintain full
customizability.
For example:
- For users of libbitcoinkernel, where we eventually want to remove
ArgsManager, they simply won't call (or even know about)
ApplyArgsManOptions.
- See src/init.cpp to see how the check_ratio CTxMemPool option works
after this change.
A MemPoolOptionsForTest helper was also added and used by tests/fuzz
tests where a local CTxMemPool needed to be created.
The change in src/test/fuzz/tx_pool.cpp seemingly changes behaviour by
applying ArgsManager options on top of the CTxMemPool::Options defaults.
However, in future commits where we introduce flags like -maxmempool,
the call to ApplyArgsManOptions is actually what preserves the existing
behaviour. Previously, although it wasn't obvious, our CTxMemPool would
consult gArgs for flags like -maxmempool when it needed it, so it
already relied on ArgsManager information. This patchset just laid bare
the obfuscatory perils of globals.
[META] As this patchset progresses, we will move more and more
CTxMemPool-relevant options into MemPoolOptions and add their
ArgsMan-related logic to ApplyArgsManOptions.
2022-03-18 13:51:37 -04:00
|
|
|
|
2024-01-12 11:05:05 -05:00
|
|
|
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS)
|
pool: Add and use MemPoolOptions, ApplyArgsManOptions
Reviewers: Note that CTxMemPool now requires a non-defaulted
CTxMemPool::Options for its constructor. Meaning that there's no need to
worry about a stray CTxMemPool constructor somewhere defaulting to
something incorrect. All instances of CTxMemPool construction are
addressed here in this commit.
We set options for CTxMemPool and construct it in many different ways. A
good example can be seen in how we determine CTxMemPool's check_ratio in
AppInitMain(...).
1. We first set the default based on chainparams's
DefaultConsistencyChecks()
2. Then, we apply the ArgsManager option on top of that default
3. Finally, we clamp the result of that between 0 and 1 Million
With this patch, most CTxMemPool construction are along the lines of:
MemPoolOptions mempool_opts{...default overrides...};
ApplyArgsManOptions(argsman, mempool_opts);
...hard overrides...
CTxMemPool pool{mempool_opts};
This "compositional" style of building options means that we can omit
unnecessary/irrelevant steps wherever we want but also maintain full
customizability.
For example:
- For users of libbitcoinkernel, where we eventually want to remove
ArgsManager, they simply won't call (or even know about)
ApplyArgsManOptions.
- See src/init.cpp to see how the check_ratio CTxMemPool option works
after this change.
A MemPoolOptionsForTest helper was also added and used by tests/fuzz
tests where a local CTxMemPool needed to be created.
The change in src/test/fuzz/tx_pool.cpp seemingly changes behaviour by
applying ArgsManager options on top of the CTxMemPool::Options defaults.
However, in future commits where we introduce flags like -maxmempool,
the call to ApplyArgsManOptions is actually what preserves the existing
behaviour. Previously, although it wasn't obvious, our CTxMemPool would
consult gArgs for flags like -maxmempool when it needed it, so it
already relied on ArgsManager information. This patchset just laid bare
the obfuscatory perils of globals.
[META] As this patchset progresses, we will move more and more
CTxMemPool-relevant options into MemPoolOptions and add their
ArgsMan-related logic to ApplyArgsManOptions.
2022-03-18 13:51:37 -04:00
|
|
|
{
|
2023-09-07 19:16:57 +10:00
|
|
|
const std::optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
|
2020-04-26 19:29:03 +00:00
|
|
|
if (!another_mtx) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
const CTransaction another_tx{*another_mtx};
|
|
|
|
|
if (fuzzed_data_provider.ConsumeBool() && !mtx->vin.empty()) {
|
|
|
|
|
mtx->vin[0].prevout = COutPoint{another_tx.GetHash(), 0};
|
|
|
|
|
}
|
|
|
|
|
LOCK2(cs_main, pool.cs);
|
2024-10-19 10:18:53 -04:00
|
|
|
if (!pool.GetIter(another_tx.GetHash())) {
|
|
|
|
|
AddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, another_tx));
|
|
|
|
|
}
|
2020-04-26 19:29:03 +00:00
|
|
|
}
|
|
|
|
|
const CTransaction tx{*mtx};
|
|
|
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
|
|
|
LOCK2(cs_main, pool.cs);
|
2024-10-19 10:18:53 -04:00
|
|
|
if (!pool.GetIter(tx.GetHash())) {
|
|
|
|
|
AddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, tx));
|
|
|
|
|
}
|
2020-04-26 19:29:03 +00:00
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
LOCK(pool.cs);
|
|
|
|
|
(void)IsRBFOptIn(tx, pool);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-12 11:05:05 -05:00
|
|
|
|
|
|
|
|
FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)
|
|
|
|
|
{
|
2024-12-13 14:22:55 +01:00
|
|
|
SeedRandomStateForTest(SeedRand::ZEROS);
|
2024-01-12 11:05:05 -05:00
|
|
|
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
|
|
|
|
|
SetMockTime(ConsumeTime(fuzzed_data_provider));
|
|
|
|
|
|
2024-06-18 10:13:08 -04:00
|
|
|
// "Real" virtual size is not important for this test since ConsumeTxMemPoolEntry generates its own virtual size values
|
|
|
|
|
// so we construct small transactions for performance reasons. Child simply needs an input for later to perhaps connect to parent.
|
|
|
|
|
CMutableTransaction child;
|
|
|
|
|
child.vin.resize(1);
|
2024-01-12 11:05:05 -05:00
|
|
|
|
2024-04-21 10:03:21 +02:00
|
|
|
bilingual_str error;
|
|
|
|
|
CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
|
|
|
|
|
Assert(error.empty());
|
2024-01-12 11:05:05 -05:00
|
|
|
|
|
|
|
|
// Add a bunch of parent-child pairs to the mempool, and remember them.
|
|
|
|
|
std::vector<CTransaction> mempool_txs;
|
|
|
|
|
size_t iter{0};
|
|
|
|
|
|
2024-04-15 16:27:21 +02:00
|
|
|
// Keep track of the total vsize of CTxMemPoolEntry's being added to the mempool to avoid overflow
|
|
|
|
|
// Add replacement_vsize since this is added to new diagram during RBF check
|
2024-10-16 09:09:44 -04:00
|
|
|
std::optional<CMutableTransaction> replacement_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
|
|
|
|
|
if (!replacement_tx) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
assert(iter <= g_outpoints.size());
|
|
|
|
|
replacement_tx->vin.resize(1);
|
|
|
|
|
replacement_tx->vin[0].prevout = g_outpoints[iter++];
|
|
|
|
|
CTransaction replacement_tx_final{*replacement_tx};
|
|
|
|
|
auto replacement_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, replacement_tx_final);
|
|
|
|
|
int32_t replacement_vsize = replacement_entry.GetTxSize();
|
2024-04-15 16:27:21 +02:00
|
|
|
int64_t running_vsize_total{replacement_vsize};
|
|
|
|
|
|
2024-01-12 11:05:05 -05:00
|
|
|
LOCK2(cs_main, pool.cs);
|
|
|
|
|
|
|
|
|
|
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS)
|
|
|
|
|
{
|
|
|
|
|
// Make sure txns only have one input, and that a unique input is given to avoid circular references
|
2024-06-18 10:13:08 -04:00
|
|
|
CMutableTransaction parent;
|
2024-01-12 11:05:05 -05:00
|
|
|
assert(iter <= g_outpoints.size());
|
2024-06-18 10:13:08 -04:00
|
|
|
parent.vin.resize(1);
|
|
|
|
|
parent.vin[0].prevout = g_outpoints[iter++];
|
2026-03-02 10:52:53 +00:00
|
|
|
parent.vout.emplace_back(CAsset(), 0, CScript());
|
2024-01-12 11:05:05 -05:00
|
|
|
|
2024-06-18 10:13:08 -04:00
|
|
|
mempool_txs.emplace_back(parent);
|
2024-04-15 16:27:21 +02:00
|
|
|
const auto parent_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());
|
|
|
|
|
running_vsize_total += parent_entry.GetTxSize();
|
|
|
|
|
if (running_vsize_total > std::numeric_limits<int32_t>::max()) {
|
|
|
|
|
// We aren't adding this final tx to mempool, so we don't want to conflict with it
|
|
|
|
|
mempool_txs.pop_back();
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-10-19 10:18:53 -04:00
|
|
|
assert(!pool.GetIter(parent_entry.GetTx().GetHash()));
|
2024-10-10 14:59:23 -04:00
|
|
|
AddToMempool(pool, parent_entry);
|
2024-06-18 10:13:08 -04:00
|
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
|
|
|
child.vin[0].prevout = COutPoint{mempool_txs.back().GetHash(), 0};
|
2024-01-12 11:05:05 -05:00
|
|
|
}
|
2024-06-18 10:13:08 -04:00
|
|
|
mempool_txs.emplace_back(child);
|
2024-04-15 16:27:21 +02:00
|
|
|
const auto child_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());
|
|
|
|
|
running_vsize_total += child_entry.GetTxSize();
|
|
|
|
|
if (running_vsize_total > std::numeric_limits<int32_t>::max()) {
|
|
|
|
|
// We aren't adding this final tx to mempool, so we don't want to conflict with it
|
|
|
|
|
mempool_txs.pop_back();
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-10-19 10:18:53 -04:00
|
|
|
if (!pool.GetIter(child_entry.GetTx().GetHash())) {
|
|
|
|
|
AddToMempool(pool, child_entry);
|
|
|
|
|
}
|
2024-03-25 10:34:31 -04:00
|
|
|
|
|
|
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
|
|
|
pool.PrioritiseTransaction(mempool_txs.back().GetHash().ToUint256(), fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(-100000, 100000));
|
|
|
|
|
}
|
2024-01-12 11:05:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pick some transactions at random to be the direct conflicts
|
|
|
|
|
CTxMemPool::setEntries direct_conflicts;
|
|
|
|
|
for (auto& tx : mempool_txs) {
|
|
|
|
|
if (fuzzed_data_provider.ConsumeBool()) {
|
|
|
|
|
direct_conflicts.insert(*pool.GetIter(tx.GetHash()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate all conflicts:
|
|
|
|
|
CTxMemPool::setEntries all_conflicts;
|
|
|
|
|
for (auto& txiter : direct_conflicts) {
|
|
|
|
|
pool.CalculateDescendants(txiter, all_conflicts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CAmount replacement_fees = ConsumeMoney(fuzzed_data_provider);
|
2024-10-16 09:09:44 -04:00
|
|
|
auto changeset = pool.GetChangeSet();
|
|
|
|
|
for (auto& txiter : all_conflicts) {
|
|
|
|
|
changeset->StageRemoval(txiter);
|
|
|
|
|
}
|
|
|
|
|
changeset->StageAddition(replacement_entry.GetSharedTx(), replacement_fees,
|
|
|
|
|
replacement_entry.GetTime().count(), replacement_entry.GetHeight(),
|
|
|
|
|
replacement_entry.GetSequence(), replacement_entry.GetSpendsCoinbase(),
|
|
|
|
|
replacement_entry.GetSigOpCost(), replacement_entry.GetLockPoints());
|
|
|
|
|
// Calculate the chunks for a replacement.
|
|
|
|
|
auto calc_results{changeset->CalculateChunksForRBF()};
|
2024-01-12 11:05:05 -05:00
|
|
|
|
|
|
|
|
if (calc_results.has_value()) {
|
2024-03-17 09:42:12 -04:00
|
|
|
// Sanity checks on the chunks.
|
2024-01-12 11:05:05 -05:00
|
|
|
|
2024-03-17 09:42:12 -04:00
|
|
|
// Feerates are monotonically decreasing.
|
|
|
|
|
FeeFrac first_sum;
|
|
|
|
|
for (size_t i = 0; i < calc_results->first.size(); ++i) {
|
|
|
|
|
first_sum += calc_results->first[i];
|
|
|
|
|
if (i) assert(!(calc_results->first[i - 1] << calc_results->first[i]));
|
|
|
|
|
}
|
|
|
|
|
FeeFrac second_sum;
|
|
|
|
|
for (size_t i = 0; i < calc_results->second.size(); ++i) {
|
|
|
|
|
second_sum += calc_results->second[i];
|
|
|
|
|
if (i) assert(!(calc_results->second[i - 1] << calc_results->second[i]));
|
|
|
|
|
}
|
2024-01-12 11:05:05 -05:00
|
|
|
|
2024-03-17 09:42:12 -04:00
|
|
|
FeeFrac replaced;
|
2024-01-12 11:05:05 -05:00
|
|
|
for (auto txiter : all_conflicts) {
|
2024-03-17 09:42:12 -04:00
|
|
|
replaced.fee += txiter->GetModifiedFee();
|
|
|
|
|
replaced.size += txiter->GetTxSize();
|
2024-01-12 11:05:05 -05:00
|
|
|
}
|
2024-03-17 09:42:12 -04:00
|
|
|
// The total fee & size of the new diagram minus replaced fee & size should be the total
|
|
|
|
|
// fee & size of the old diagram minus replacement fee & size.
|
|
|
|
|
assert((first_sum - replaced) == (second_sum - FeeFrac{replacement_fees, replacement_vsize}));
|
2024-01-12 11:05:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If internals report error, wrapper should too
|
2024-10-16 09:09:44 -04:00
|
|
|
auto err_tuple{ImprovesFeerateDiagram(*changeset)};
|
2024-03-25 12:01:32 -04:00
|
|
|
if (!calc_results.has_value()) {
|
|
|
|
|
assert(err_tuple.value().first == DiagramCheckError::UNCALCULABLE);
|
|
|
|
|
} else {
|
|
|
|
|
// Diagram check succeeded
|
2024-03-17 09:42:12 -04:00
|
|
|
auto old_sum = std::accumulate(calc_results->first.begin(), calc_results->first.end(), FeeFrac{});
|
|
|
|
|
auto new_sum = std::accumulate(calc_results->second.begin(), calc_results->second.end(), FeeFrac{});
|
2024-03-25 12:01:32 -04:00
|
|
|
if (!err_tuple.has_value()) {
|
|
|
|
|
// New diagram's final fee should always match or exceed old diagram's
|
2024-03-17 09:42:12 -04:00
|
|
|
assert(old_sum.fee <= new_sum.fee);
|
|
|
|
|
} else if (old_sum.fee > new_sum.fee) {
|
2024-03-25 12:01:32 -04:00
|
|
|
// Or it failed, and if old diagram had higher fees, it should be a failure
|
|
|
|
|
assert(err_tuple.value().first == DiagramCheckError::FAILURE);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-12 11:05:05 -05:00
|
|
|
}
|