rbf.cpp
1 // Copyright (c) 2020-2022 The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <node/mempool_args.h> 6 #include <policy/rbf.h> 7 #include <primitives/transaction.h> 8 #include <sync.h> 9 #include <test/fuzz/FuzzedDataProvider.h> 10 #include <test/fuzz/fuzz.h> 11 #include <test/fuzz/util.h> 12 #include <test/fuzz/util/mempool.h> 13 #include <test/util/setup_common.h> 14 #include <test/util/txmempool.h> 15 #include <txmempool.h> 16 17 #include <cstdint> 18 #include <optional> 19 #include <string> 20 #include <vector> 21 22 namespace { 23 const BasicTestingSetup* g_setup; 24 } // namespace 25 26 const int NUM_ITERS = 10000; 27 28 std::vector<COutPoint> g_outpoints; 29 30 void initialize_rbf() 31 { 32 static const auto testing_setup = MakeNoLogFileContext<>(); 33 g_setup = testing_setup.get(); 34 } 35 36 void initialize_package_rbf() 37 { 38 static const auto testing_setup = MakeNoLogFileContext<>(); 39 g_setup = testing_setup.get(); 40 41 // Create a fixed set of unique "UTXOs" to source parents from 42 // to avoid fuzzer giving circular references 43 for (int i = 0; i < NUM_ITERS; ++i) { 44 g_outpoints.emplace_back(); 45 g_outpoints.back().n = i; 46 } 47 48 } 49 50 FUZZ_TARGET(rbf, .init = initialize_rbf) 51 { 52 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 53 SetMockTime(ConsumeTime(fuzzed_data_provider)); 54 std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS); 55 if (!mtx) { 56 return; 57 } 58 59 CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node)}; 60 61 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS) 62 { 63 const std::optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS); 64 if (!another_mtx) { 65 break; 66 } 67 const CTransaction another_tx{*another_mtx}; 68 if (fuzzed_data_provider.ConsumeBool() && !mtx->vin.empty()) { 69 mtx->vin[0].prevout = COutPoint{another_tx.GetHash(), 0}; 70 } 71 LOCK2(cs_main, pool.cs); 72 pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, another_tx)); 73 } 74 const CTransaction tx{*mtx}; 75 if (fuzzed_data_provider.ConsumeBool()) { 76 LOCK2(cs_main, pool.cs); 77 pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx)); 78 } 79 { 80 LOCK(pool.cs); 81 (void)IsRBFOptIn(tx, pool); 82 } 83 } 84 85 void CheckDiagramConcave(std::vector<FeeFrac>& diagram) 86 { 87 // Diagrams are in monotonically-decreasing feerate order. 88 FeeFrac last_chunk = diagram.front(); 89 for (size_t i = 1; i<diagram.size(); ++i) { 90 FeeFrac next_chunk = diagram[i] - diagram[i-1]; 91 assert(next_chunk <= last_chunk); 92 last_chunk = next_chunk; 93 } 94 } 95 96 FUZZ_TARGET(package_rbf, .init = initialize_package_rbf) 97 { 98 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 99 SetMockTime(ConsumeTime(fuzzed_data_provider)); 100 101 std::optional<CMutableTransaction> child = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS); 102 if (!child) return; 103 104 CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node)}; 105 106 // Add a bunch of parent-child pairs to the mempool, and remember them. 107 std::vector<CTransaction> mempool_txs; 108 size_t iter{0}; 109 110 LOCK2(cs_main, pool.cs); 111 112 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), NUM_ITERS) 113 { 114 // Make sure txns only have one input, and that a unique input is given to avoid circular references 115 std::optional<CMutableTransaction> parent = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS); 116 if (!parent) { 117 continue; 118 } 119 assert(iter <= g_outpoints.size()); 120 parent->vin.resize(1); 121 parent->vin[0].prevout = g_outpoints[iter++]; 122 123 mempool_txs.emplace_back(*parent); 124 pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back())); 125 if (fuzzed_data_provider.ConsumeBool() && !child->vin.empty()) { 126 child->vin[0].prevout = COutPoint{mempool_txs.back().GetHash(), 0}; 127 } 128 mempool_txs.emplace_back(*child); 129 pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back())); 130 } 131 132 // Pick some transactions at random to be the direct conflicts 133 CTxMemPool::setEntries direct_conflicts; 134 for (auto& tx : mempool_txs) { 135 if (fuzzed_data_provider.ConsumeBool()) { 136 direct_conflicts.insert(*pool.GetIter(tx.GetHash())); 137 } 138 } 139 140 // Calculate all conflicts: 141 CTxMemPool::setEntries all_conflicts; 142 for (auto& txiter : direct_conflicts) { 143 pool.CalculateDescendants(txiter, all_conflicts); 144 } 145 146 // Calculate the feerate diagrams for a replacement. 147 CAmount replacement_fees = ConsumeMoney(fuzzed_data_provider); 148 int64_t replacement_vsize = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(1, 1000000); 149 auto calc_results{pool.CalculateFeerateDiagramsForRBF(replacement_fees, replacement_vsize, direct_conflicts, all_conflicts)}; 150 151 if (calc_results.has_value()) { 152 // Sanity checks on the diagrams. 153 154 // Diagrams start at 0. 155 assert(calc_results->first.front().size == 0); 156 assert(calc_results->first.front().fee == 0); 157 assert(calc_results->second.front().size == 0); 158 assert(calc_results->second.front().fee == 0); 159 160 CheckDiagramConcave(calc_results->first); 161 CheckDiagramConcave(calc_results->second); 162 163 CAmount replaced_fee{0}; 164 int64_t replaced_size{0}; 165 for (auto txiter : all_conflicts) { 166 replaced_fee += txiter->GetModifiedFee(); 167 replaced_size += txiter->GetTxSize(); 168 } 169 // The total fee of the new diagram should be the total fee of the old 170 // diagram - replaced_fee + replacement_fees 171 assert(calc_results->first.back().fee - replaced_fee + replacement_fees == calc_results->second.back().fee); 172 assert(calc_results->first.back().size - replaced_size + replacement_vsize == calc_results->second.back().size); 173 } 174 175 // If internals report error, wrapper should too 176 auto err_tuple{ImprovesFeerateDiagram(pool, direct_conflicts, all_conflicts, replacement_fees, replacement_vsize)}; 177 if (!calc_results.has_value()) assert(err_tuple.has_value()); 178 }