2024-07-17 16:45:23 +02:00
|
|
|
// Copyright (c) 2022-present The Bitcoin Core developers
|
2022-06-22 20:44:43 +08:00
|
|
|
// Distributed under the MIT software license, see the accompanying
|
|
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
|
|
|
|
|
#include <consensus/amount.h>
|
2022-07-16 09:41:50 +08:00
|
|
|
#include <consensus/validation.h>
|
2022-06-22 20:44:43 +08:00
|
|
|
#include <net_processing.h>
|
2022-07-16 09:41:50 +08:00
|
|
|
#include <node/eviction.h>
|
|
|
|
|
#include <policy/policy.h>
|
2022-06-22 20:44:43 +08:00
|
|
|
#include <primitives/transaction.h>
|
|
|
|
|
#include <script/script.h>
|
|
|
|
|
#include <sync.h>
|
|
|
|
|
#include <test/fuzz/FuzzedDataProvider.h>
|
|
|
|
|
#include <test/fuzz/fuzz.h>
|
|
|
|
|
#include <test/fuzz/util.h>
|
|
|
|
|
#include <test/util/setup_common.h>
|
|
|
|
|
#include <txorphanage.h>
|
|
|
|
|
#include <uint256.h>
|
|
|
|
|
#include <util/check.h>
|
|
|
|
|
#include <util/time.h>
|
|
|
|
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <set>
|
|
|
|
|
#include <utility>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
void initialize_orphanage()
|
|
|
|
|
{
|
|
|
|
|
static const auto testing_setup = MakeNoLogFileContext();
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-11 14:33:31 +02:00
|
|
|
FUZZ_TARGET(txorphan, .init = initialize_orphanage)
|
2022-06-22 20:44:43 +08:00
|
|
|
{
|
|
|
|
|
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
|
2025-01-21 17:59:37 -05:00
|
|
|
FastRandomContext orphanage_rng{/*fDeterministic=*/true};
|
2022-06-22 20:44:43 +08:00
|
|
|
SetMockTime(ConsumeTime(fuzzed_data_provider));
|
|
|
|
|
|
|
|
|
|
TxOrphanage orphanage;
|
2024-07-17 16:45:23 +02:00
|
|
|
std::vector<COutPoint> outpoints; // Duplicates are tolerated
|
2024-11-18 16:23:36 +01:00
|
|
|
outpoints.reserve(200'000);
|
fuzz: txorphan tests fixups
Adds the following fixups in txorphan fuzz tests:
- Don't bond the output count of the created orphans based on the number of available coins
- Allow duplicate inputs, when applicable, but don't store duplicate outpoints
Rationale
---------
The way the test is currently written, duplicate inputs are allowed based on a random flag (`duplicate_input`).
If the flag is unset, upon selecting an outpoint as input for a new transaction, the input is popped to prevent re-selection,
and later re-added to the collection (once all inputs have been picked). However, the re-addition to the collection is performed independently of whether the flag was set or not.
This means that, if the flag is set, the selected inputs are duplicated which in turn makes these inputs more likely to be re-picked in the following iteration of the loop.
Additionally, both the input and output count of the transaction and bonded to the number of available outpoints. This makes sense for the former, but the latter shouldn't be.
2024-04-26 14:28:53 -04:00
|
|
|
|
2022-06-22 20:44:43 +08:00
|
|
|
// initial outpoints used to construct transactions later
|
|
|
|
|
for (uint8_t i = 0; i < 4; i++) {
|
2024-07-17 16:45:23 +02:00
|
|
|
outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0);
|
2022-06-22 20:44:43 +08:00
|
|
|
}
|
|
|
|
|
|
2024-03-28 17:15:11 +00:00
|
|
|
CTransactionRef ptx_potential_parent = nullptr;
|
|
|
|
|
|
2022-09-30 17:00:11 +02:00
|
|
|
LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS)
|
2022-06-22 20:44:43 +08:00
|
|
|
{
|
|
|
|
|
// construct transaction
|
|
|
|
|
const CTransactionRef tx = [&] {
|
|
|
|
|
CMutableTransaction tx_mut;
|
|
|
|
|
const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size());
|
fuzz: txorphan tests fixups
Adds the following fixups in txorphan fuzz tests:
- Don't bond the output count of the created orphans based on the number of available coins
- Allow duplicate inputs, when applicable, but don't store duplicate outpoints
Rationale
---------
The way the test is currently written, duplicate inputs are allowed based on a random flag (`duplicate_input`).
If the flag is unset, upon selecting an outpoint as input for a new transaction, the input is popped to prevent re-selection,
and later re-added to the collection (once all inputs have been picked). However, the re-addition to the collection is performed independently of whether the flag was set or not.
This means that, if the flag is set, the selected inputs are duplicated which in turn makes these inputs more likely to be re-picked in the following iteration of the loop.
Additionally, both the input and output count of the transaction and bonded to the number of available outpoints. This makes sense for the former, but the latter shouldn't be.
2024-04-26 14:28:53 -04:00
|
|
|
const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256);
|
|
|
|
|
// pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not
|
|
|
|
|
// running any transaction validation logic before adding transactions to the orphanage
|
2024-11-18 16:23:36 +01:00
|
|
|
tx_mut.vin.reserve(num_in);
|
2022-06-22 20:44:43 +08:00
|
|
|
for (uint32_t i = 0; i < num_in; i++) {
|
|
|
|
|
auto& prevout = PickValue(fuzzed_data_provider, outpoints);
|
fuzz: txorphan tests fixups
Adds the following fixups in txorphan fuzz tests:
- Don't bond the output count of the created orphans based on the number of available coins
- Allow duplicate inputs, when applicable, but don't store duplicate outpoints
Rationale
---------
The way the test is currently written, duplicate inputs are allowed based on a random flag (`duplicate_input`).
If the flag is unset, upon selecting an outpoint as input for a new transaction, the input is popped to prevent re-selection,
and later re-added to the collection (once all inputs have been picked). However, the re-addition to the collection is performed independently of whether the flag was set or not.
This means that, if the flag is set, the selected inputs are duplicated which in turn makes these inputs more likely to be re-picked in the following iteration of the loop.
Additionally, both the input and output count of the transaction and bonded to the number of available outpoints. This makes sense for the former, but the latter shouldn't be.
2024-04-26 14:28:53 -04:00
|
|
|
// try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen
|
|
|
|
|
tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL));
|
2022-06-22 20:44:43 +08:00
|
|
|
}
|
|
|
|
|
// output amount will not affect txorphanage
|
2024-11-18 16:23:36 +01:00
|
|
|
tx_mut.vout.reserve(num_out);
|
2022-06-22 20:44:43 +08:00
|
|
|
for (uint32_t i = 0; i < num_out; i++) {
|
|
|
|
|
tx_mut.vout.emplace_back(CAmount{0}, CScript{});
|
|
|
|
|
}
|
2022-12-27 15:25:51 +00:00
|
|
|
auto new_tx = MakeTransactionRef(tx_mut);
|
fuzz: txorphan tests fixups
Adds the following fixups in txorphan fuzz tests:
- Don't bond the output count of the created orphans based on the number of available coins
- Allow duplicate inputs, when applicable, but don't store duplicate outpoints
Rationale
---------
The way the test is currently written, duplicate inputs are allowed based on a random flag (`duplicate_input`).
If the flag is unset, upon selecting an outpoint as input for a new transaction, the input is popped to prevent re-selection,
and later re-added to the collection (once all inputs have been picked). However, the re-addition to the collection is performed independently of whether the flag was set or not.
This means that, if the flag is set, the selected inputs are duplicated which in turn makes these inputs more likely to be re-picked in the following iteration of the loop.
Additionally, both the input and output count of the transaction and bonded to the number of available outpoints. This makes sense for the former, but the latter shouldn't be.
2024-04-26 14:28:53 -04:00
|
|
|
// add newly constructed outpoints to the coin pool
|
2022-06-22 20:44:43 +08:00
|
|
|
for (uint32_t i = 0; i < num_out; i++) {
|
2024-07-17 16:45:23 +02:00
|
|
|
outpoints.emplace_back(new_tx->GetHash(), i);
|
2022-06-22 20:44:43 +08:00
|
|
|
}
|
|
|
|
|
return new_tx;
|
|
|
|
|
}();
|
|
|
|
|
|
2025-01-27 08:17:30 -05:00
|
|
|
const auto wtxid{tx->GetWitnessHash()};
|
|
|
|
|
|
2024-03-28 17:15:11 +00:00
|
|
|
// Trigger orphanage functions that are called using parents. ptx_potential_parent is a tx we constructed in a
|
|
|
|
|
// previous loop and potentially the parent of this tx.
|
|
|
|
|
if (ptx_potential_parent) {
|
|
|
|
|
// Set up future GetTxToReconsider call.
|
2025-01-21 17:59:37 -05:00
|
|
|
orphanage.AddChildrenToWorkSet(*ptx_potential_parent, orphanage_rng);
|
2024-03-28 17:15:11 +00:00
|
|
|
|
|
|
|
|
// Check that all txns returned from GetChildrenFrom* are indeed a direct child of this tx.
|
|
|
|
|
NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();
|
|
|
|
|
for (const auto& child : orphanage.GetChildrenFromSamePeer(ptx_potential_parent, peer_id)) {
|
|
|
|
|
assert(std::any_of(child->vin.cbegin(), child->vin.cend(), [&](const auto& input) {
|
|
|
|
|
return input.prevout.hash == ptx_potential_parent->GetHash();
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-22 20:44:43 +08:00
|
|
|
// trigger orphanage functions
|
|
|
|
|
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS)
|
|
|
|
|
{
|
|
|
|
|
NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();
|
2025-01-27 08:17:30 -05:00
|
|
|
const auto total_bytes_start{orphanage.TotalOrphanUsage()};
|
|
|
|
|
const auto total_peer_bytes_start{orphanage.UsageByPeer(peer_id)};
|
|
|
|
|
const auto tx_weight{GetTransactionWeight(*tx)};
|
2022-06-22 20:44:43 +08:00
|
|
|
|
|
|
|
|
CallOneOf(
|
|
|
|
|
fuzzed_data_provider,
|
|
|
|
|
[&] {
|
|
|
|
|
{
|
2023-01-25 18:13:00 +10:00
|
|
|
CTransactionRef ref = orphanage.GetTxToReconsider(peer_id);
|
|
|
|
|
if (ref) {
|
2024-05-10 10:55:39 +01:00
|
|
|
Assert(orphanage.HaveTx(ref->GetWitnessHash()));
|
2021-02-25 00:28:16 +10:00
|
|
|
}
|
2022-06-22 20:44:43 +08:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[&] {
|
2024-05-10 10:55:39 +01:00
|
|
|
bool have_tx = orphanage.HaveTx(tx->GetWitnessHash());
|
2022-06-22 20:44:43 +08:00
|
|
|
// AddTx should return false if tx is too big or already have it
|
2022-07-16 09:41:50 +08:00
|
|
|
// tx weight is unknown, we only check when tx is already in orphanage
|
2022-06-22 20:44:43 +08:00
|
|
|
{
|
2022-07-16 09:41:50 +08:00
|
|
|
bool add_tx = orphanage.AddTx(tx, peer_id);
|
|
|
|
|
// have_tx == true -> add_tx == false
|
|
|
|
|
Assert(!have_tx || !add_tx);
|
2025-01-27 08:17:30 -05:00
|
|
|
|
|
|
|
|
if (add_tx) {
|
|
|
|
|
Assert(orphanage.UsageByPeer(peer_id) == tx_weight + total_peer_bytes_start);
|
|
|
|
|
Assert(orphanage.TotalOrphanUsage() == tx_weight + total_bytes_start);
|
|
|
|
|
Assert(tx_weight <= MAX_STANDARD_TX_WEIGHT);
|
|
|
|
|
} else {
|
|
|
|
|
// Peer may have been added as an announcer.
|
|
|
|
|
if (orphanage.UsageByPeer(peer_id) == tx_weight + total_peer_bytes_start) {
|
|
|
|
|
Assert(orphanage.HaveTxFromPeer(wtxid, peer_id));
|
|
|
|
|
} else {
|
|
|
|
|
// Otherwise, there must not be any change to the peer byte count.
|
|
|
|
|
Assert(orphanage.UsageByPeer(peer_id) == total_peer_bytes_start);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Regardless, total bytes should not have changed.
|
|
|
|
|
Assert(orphanage.TotalOrphanUsage() == total_bytes_start);
|
|
|
|
|
}
|
2022-06-22 20:44:43 +08:00
|
|
|
}
|
2024-05-10 10:55:39 +01:00
|
|
|
have_tx = orphanage.HaveTx(tx->GetWitnessHash());
|
2022-06-22 20:44:43 +08:00
|
|
|
{
|
2022-07-16 09:41:50 +08:00
|
|
|
bool add_tx = orphanage.AddTx(tx, peer_id);
|
|
|
|
|
// if have_tx is still false, it must be too big
|
2025-01-27 08:17:30 -05:00
|
|
|
Assert(!have_tx == (tx_weight > MAX_STANDARD_TX_WEIGHT));
|
2022-07-16 09:41:50 +08:00
|
|
|
Assert(!have_tx || !add_tx);
|
2022-06-22 20:44:43 +08:00
|
|
|
}
|
|
|
|
|
},
|
2024-07-31 10:52:01 +01:00
|
|
|
[&] {
|
|
|
|
|
bool have_tx = orphanage.HaveTx(tx->GetWitnessHash());
|
|
|
|
|
bool have_tx_and_peer = orphanage.HaveTxFromPeer(tx->GetWitnessHash(), peer_id);
|
|
|
|
|
// AddAnnouncer should return false if tx doesn't exist or we already HaveTxFromPeer.
|
|
|
|
|
{
|
|
|
|
|
bool added_announcer = orphanage.AddAnnouncer(tx->GetWitnessHash(), peer_id);
|
|
|
|
|
// have_tx == false -> added_announcer == false
|
|
|
|
|
Assert(have_tx || !added_announcer);
|
|
|
|
|
// have_tx_and_peer == true -> added_announcer == false
|
|
|
|
|
Assert(!have_tx_and_peer || !added_announcer);
|
2025-01-27 08:17:30 -05:00
|
|
|
|
|
|
|
|
// Total bytes should not have changed. If peer was added as announcer, byte
|
|
|
|
|
// accounting must have been updated.
|
|
|
|
|
Assert(orphanage.TotalOrphanUsage() == total_bytes_start);
|
|
|
|
|
if (added_announcer) {
|
|
|
|
|
Assert(orphanage.UsageByPeer(peer_id) == tx_weight + total_peer_bytes_start);
|
|
|
|
|
} else {
|
|
|
|
|
Assert(orphanage.UsageByPeer(peer_id) == total_peer_bytes_start);
|
|
|
|
|
}
|
2024-07-31 10:52:01 +01:00
|
|
|
}
|
|
|
|
|
},
|
2022-06-22 20:44:43 +08:00
|
|
|
[&] {
|
2024-05-10 10:55:39 +01:00
|
|
|
bool have_tx = orphanage.HaveTx(tx->GetWitnessHash());
|
2025-01-27 08:17:30 -05:00
|
|
|
bool have_tx_and_peer{orphanage.HaveTxFromPeer(wtxid, peer_id)};
|
2022-06-22 20:44:43 +08:00
|
|
|
// EraseTx should return 0 if m_orphans doesn't have the tx
|
|
|
|
|
{
|
2025-01-27 08:17:30 -05:00
|
|
|
auto bytes_from_peer_before{orphanage.UsageByPeer(peer_id)};
|
2024-05-10 12:04:50 +01:00
|
|
|
Assert(have_tx == orphanage.EraseTx(tx->GetWitnessHash()));
|
2025-01-27 08:17:30 -05:00
|
|
|
if (have_tx) {
|
|
|
|
|
Assert(orphanage.TotalOrphanUsage() == total_bytes_start - tx_weight);
|
|
|
|
|
if (have_tx_and_peer) {
|
|
|
|
|
Assert(orphanage.UsageByPeer(peer_id) == bytes_from_peer_before - tx_weight);
|
|
|
|
|
} else {
|
|
|
|
|
Assert(orphanage.UsageByPeer(peer_id) == bytes_from_peer_before);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Assert(orphanage.TotalOrphanUsage() == total_bytes_start);
|
|
|
|
|
}
|
2022-06-22 20:44:43 +08:00
|
|
|
}
|
2024-05-10 10:55:39 +01:00
|
|
|
have_tx = orphanage.HaveTx(tx->GetWitnessHash());
|
2025-01-27 08:17:30 -05:00
|
|
|
have_tx_and_peer = orphanage.HaveTxFromPeer(wtxid, peer_id);
|
2022-06-22 20:44:43 +08:00
|
|
|
// have_tx should be false and EraseTx should fail
|
|
|
|
|
{
|
2025-01-27 08:17:30 -05:00
|
|
|
Assert(!have_tx && !have_tx_and_peer && !orphanage.EraseTx(wtxid));
|
2022-06-22 20:44:43 +08:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[&] {
|
|
|
|
|
orphanage.EraseForPeer(peer_id);
|
2024-07-31 10:52:01 +01:00
|
|
|
Assert(!orphanage.HaveTxFromPeer(tx->GetWitnessHash(), peer_id));
|
2025-01-27 08:17:30 -05:00
|
|
|
Assert(orphanage.UsageByPeer(peer_id) == 0);
|
2022-06-22 20:44:43 +08:00
|
|
|
},
|
|
|
|
|
[&] {
|
|
|
|
|
// test mocktime and expiry
|
|
|
|
|
SetMockTime(ConsumeTime(fuzzed_data_provider));
|
|
|
|
|
auto limit = fuzzed_data_provider.ConsumeIntegral<unsigned int>();
|
2025-01-21 17:59:37 -05:00
|
|
|
orphanage.LimitOrphans(limit, orphanage_rng);
|
2022-06-22 20:44:43 +08:00
|
|
|
Assert(orphanage.Size() <= limit);
|
|
|
|
|
});
|
2024-03-28 17:15:11 +00:00
|
|
|
|
|
|
|
|
}
|
2025-01-30 00:46:01 -05:00
|
|
|
|
2024-03-28 17:15:11 +00:00
|
|
|
// Set tx as potential parent to be used for future GetChildren() calls.
|
|
|
|
|
if (!ptx_potential_parent || fuzzed_data_provider.ConsumeBool()) {
|
|
|
|
|
ptx_potential_parent = tx;
|
2022-06-22 20:44:43 +08:00
|
|
|
}
|
2024-03-28 17:15:11 +00:00
|
|
|
|
2025-01-03 09:04:10 -05:00
|
|
|
const bool have_tx{orphanage.HaveTx(tx->GetWitnessHash())};
|
|
|
|
|
const bool get_tx_nonnull{orphanage.GetTx(tx->GetWitnessHash()) != nullptr};
|
|
|
|
|
Assert(have_tx == get_tx_nonnull);
|
2022-06-22 20:44:43 +08:00
|
|
|
}
|
2025-01-30 00:46:01 -05:00
|
|
|
orphanage.SanityCheck();
|
2022-06-22 20:44:43 +08:00
|
|
|
}
|