1 // Copyright (c) 2021 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 <consensus/validation.h> 6 #include <miner.h> 7 #include <test/fuzz/FuzzedDataProvider.h> 8 #include <test/fuzz/fuzz.h> 9 #include <test/fuzz/util.h> 10 #include <test/util/mining.h> 11 #include <test/util/script.h> 12 #include <test/util/setup_common.h> 13 #include <util/rbf.h> 14 #include <validation.h> 15 #include <validationinterface.h> 16 17 namespace { 18 19 const TestingSetup* g_setup; 20 std::vector<COutPoint> g_outpoints_coinbase_init_mature; 21 std::vector<COutPoint> g_outpoints_coinbase_init_immature; 22 23 struct MockedTxPool : public CTxMemPool { 24 void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs) 25 { 26 LOCK(cs); 27 lastRollingFeeUpdate = GetTime(); 28 blockSinceLastRollingFeeBump = true; 29 } 30 }; 31 32 void initialize_tx_pool() 33 { 34 static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); 35 g_setup = testing_setup.get(); 36 37 for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) { sigint_handler(int sig)38 CTxIn in = MineBlock(g_setup->m_node, P2WSH_OP_TRUE); 39 // Remember the txids to avoid expensive disk access later on 40 auto& outpoints = i < COINBASE_MATURITY ? 41 g_outpoints_coinbase_init_mature : 42 g_outpoints_coinbase_init_immature; 43 outpoints.push_back(in.prevout); main(int argc,char * argv[])44 } 45 SyncWithValidationInterfaceQueue(); 46 } 47 48 struct TransactionsDelta final : public CValidationInterface { 49 std::set<CTransactionRef>& m_removed; 50 std::set<CTransactionRef>& m_added; 51 52 explicit TransactionsDelta(std::set<CTransactionRef>& r, std::set<CTransactionRef>& a) 53 : m_removed{r}, m_added{a} {} 54 55 void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t /* mempool_sequence */) override 56 { 57 Assert(m_added.insert(tx).second); 58 } 59 60 void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override 61 { 62 Assert(m_removed.insert(tx).second); 63 } 64 }; 65 66 void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_provider) 67 { 68 args.ForceSetArg("-limitancestorcount", 69 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50))); 70 args.ForceSetArg("-limitancestorsize", 71 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202))); 72 args.ForceSetArg("-limitdescendantcount", 73 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50))); 74 args.ForceSetArg("-limitdescendantsize", 75 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 202))); 76 args.ForceSetArg("-maxmempool", 77 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200))); 78 args.ForceSetArg("-mempoolexpiry", 79 ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999))); 80 } 81 82 void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CChainState& chainstate) 83 { 84 WITH_LOCK(::cs_main, tx_pool.check(chainstate)); 85 { 86 BlockAssembler::Options options; 87 options.nBlockMaxWeight = fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BLOCK_WEIGHT); 88 options.blockMinFeeRate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /* max */ COIN)}; 89 auto assembler = BlockAssembler{chainstate, *static_cast<CTxMemPool*>(&tx_pool), ::Params(), options}; 90 auto block_template = assembler.CreateNewBlock(CScript{} << OP_TRUE); 91 Assert(block_template->block.vtx.size() >= 1); 92 } 93 const auto info_all = tx_pool.infoAll(); 94 if (!info_all.empty()) { 95 const auto& tx_to_remove = *PickValue(fuzzed_data_provider, info_all).tx; main()96 WITH_LOCK(tx_pool.cs, tx_pool.removeRecursive(tx_to_remove, /* dummy */ MemPoolRemovalReason::BLOCK)); 97 std::vector<uint256> all_txids; 98 tx_pool.queryHashes(all_txids); 99 assert(all_txids.size() < info_all.size()); 100 WITH_LOCK(::cs_main, tx_pool.check(chainstate)); 101 } 102 SyncWithValidationInterfaceQueue(); 103 } 104 105 void MockTime(FuzzedDataProvider& fuzzed_data_provider, const CChainState& chainstate) 106 { 107 const auto time = ConsumeTime(fuzzed_data_provider, 108 chainstate.m_chain.Tip()->GetMedianTimePast() + 1, 109 std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max()); 110 SetMockTime(time); 111 } 112 113 FUZZ_TARGET_INIT(tx_pool_standard, initialize_tx_pool) 114 { 115 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 116 const auto& node = g_setup->m_node; 117 auto& chainstate = node.chainman->ActiveChainstate(); 118 119 MockTime(fuzzed_data_provider, chainstate); 120 SetMempoolConstraints(*node.args, fuzzed_data_provider); 121 122 // All RBF-spendable outpoints 123 std::set<COutPoint> outpoints_rbf; 124 // All outpoints counting toward the total supply (subset of outpoints_rbf) 125 std::set<COutPoint> outpoints_supply; 126 for (const auto& outpoint : g_outpoints_coinbase_init_mature) { 127 Assert(outpoints_supply.insert(outpoint).second); 128 } 129 outpoints_rbf = outpoints_supply; 130 131 // The sum of the values of all spendable outpoints 132 constexpr CAmount SUPPLY_TOTAL{COINBASE_MATURITY * 50 * COIN}; 133 134 CTxMemPool tx_pool_{/* estimator */ nullptr, /* check_ratio */ 1}; 135 MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_); 136 137 // Helper to query an amount 138 const CCoinsViewMemPool amount_view{WITH_LOCK(::cs_main, return &chainstate.CoinsTip()), tx_pool}; 139 const auto GetAmount = [&](const COutPoint& outpoint) { 140 Coin c; 141 Assert(amount_view.GetCoin(outpoint, c)); 142 return c.out.nValue; 143 }; 144 145 while (fuzzed_data_provider.ConsumeBool()) { 146 { 147 // Total supply is the mempool fee + all outpoints 148 CAmount supply_now{WITH_LOCK(tx_pool.cs, return tx_pool.GetTotalFee())}; 149 for (const auto& op : outpoints_supply) { 150 supply_now += GetAmount(op); 151 } 152 Assert(supply_now == SUPPLY_TOTAL); 153 } 154 Assert(!outpoints_supply.empty()); 155 156 // Create transaction to add to the mempool 157 const CTransactionRef tx = [&] { 158 CMutableTransaction tx_mut; 159 tx_mut.nVersion = CTransaction::CURRENT_VERSION; 160 tx_mut.nLockTime = fuzzed_data_provider.ConsumeBool() ? 0 : fuzzed_data_provider.ConsumeIntegral<uint32_t>(); 161 const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size()); 162 const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<int>(1, outpoints_rbf.size() * 2); 163 164 CAmount amount_in{0}; 165 for (int i = 0; i < num_in; ++i) { 166 // Pop random outpoint 167 auto pop = outpoints_rbf.begin(); 168 std::advance(pop, fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, outpoints_rbf.size() - 1)); 169 const auto outpoint = *pop; 170 outpoints_rbf.erase(pop); 171 amount_in += GetAmount(outpoint); 172 173 // Create input 174 const auto sequence = ConsumeSequence(fuzzed_data_provider); 175 const auto script_sig = CScript{}; 176 const auto script_wit_stack = std::vector<std::vector<uint8_t>>{WITNESS_STACK_ELEM_OP_TRUE}; 177 CTxIn in; 178 in.prevout = outpoint; 179 in.nSequence = sequence; 180 in.scriptSig = script_sig; 181 in.scriptWitness.stack = script_wit_stack; 182 183 tx_mut.vin.push_back(in); 184 } 185 const auto amount_fee = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1000, amount_in); 186 const auto amount_out = (amount_in - amount_fee) / num_out; 187 for (int i = 0; i < num_out; ++i) { 188 tx_mut.vout.emplace_back(amount_out, P2WSH_OP_TRUE); 189 } 190 const auto tx = MakeTransactionRef(tx_mut); 191 // Restore previously removed outpoints 192 for (const auto& in : tx->vin) { 193 Assert(outpoints_rbf.insert(in.prevout).second); 194 } 195 return tx; 196 }(); 197 198 if (fuzzed_data_provider.ConsumeBool()) { 199 MockTime(fuzzed_data_provider, chainstate); 200 } 201 if (fuzzed_data_provider.ConsumeBool()) { 202 SetMempoolConstraints(*node.args, fuzzed_data_provider); 203 } 204 if (fuzzed_data_provider.ConsumeBool()) { 205 tx_pool.RollingFeeUpdate(); 206 } 207 if (fuzzed_data_provider.ConsumeBool()) { 208 const auto& txid = fuzzed_data_provider.ConsumeBool() ? 209 tx->GetHash() : 210 PickValue(fuzzed_data_provider, outpoints_rbf).hash; 211 const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN); 212 tx_pool.PrioritiseTransaction(txid, delta); 213 } 214 215 // Remember all removed and added transactions 216 std::set<CTransactionRef> removed; 217 std::set<CTransactionRef> added; 218 auto txr = std::make_shared<TransactionsDelta>(removed, added); 219 RegisterSharedValidationInterface(txr); 220 const bool bypass_limits = fuzzed_data_provider.ConsumeBool(); 221 ::fRequireStandard = fuzzed_data_provider.ConsumeBool(); 222 223 // Make sure ProcessNewPackage on one transaction works and always fully validates the transaction. 224 // The result is not guaranteed to be the same as what is returned by ATMP. 225 const auto result_package = WITH_LOCK(::cs_main, 226 return ProcessNewPackage(node.chainman->ActiveChainstate(), tx_pool, {tx}, true)); 227 auto it = result_package.m_tx_results.find(tx->GetWitnessHash()); 228 Assert(it != result_package.m_tx_results.end()); 229 Assert(it->second.m_result_type == MempoolAcceptResult::ResultType::VALID || 230 it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID); 231 232 const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx_pool, tx, bypass_limits)); 233 const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID; 234 SyncWithValidationInterfaceQueue(); 235 UnregisterSharedValidationInterface(txr); 236 237 Assert(accepted != added.empty()); 238 Assert(accepted == res.m_state.IsValid()); 239 Assert(accepted != res.m_state.IsInvalid()); 240 if (accepted) { 241 Assert(added.size() == 1); // For now, no package acceptance 242 Assert(tx == *added.begin()); 243 } else { 244 // Do not consider rejected transaction removed 245 removed.erase(tx); 246 } 247 248 // Helper to insert spent and created outpoints of a tx into collections 249 using Sets = std::vector<std::reference_wrapper<std::set<COutPoint>>>; 250 const auto insert_tx = [](Sets created_by_tx, Sets consumed_by_tx, const auto& tx) { 251 for (size_t i{0}; i < tx.vout.size(); ++i) { 252 for (auto& set : created_by_tx) { 253 Assert(set.get().emplace(tx.GetHash(), i).second); 254 } 255 } 256 for (const auto& in : tx.vin) { 257 for (auto& set : consumed_by_tx) { 258 Assert(set.get().insert(in.prevout).second); 259 } 260 } 261 }; 262 // Add created outpoints, remove spent outpoints 263 { 264 // Outpoints that no longer exist at all 265 std::set<COutPoint> consumed_erased; 266 // Outpoints that no longer count toward the total supply 267 std::set<COutPoint> consumed_supply; 268 for (const auto& removed_tx : removed) { 269 insert_tx(/* created_by_tx */ {consumed_erased}, /* consumed_by_tx */ {outpoints_supply}, /* tx */ *removed_tx); 270 } 271 for (const auto& added_tx : added) { 272 insert_tx(/* created_by_tx */ {outpoints_supply, outpoints_rbf}, /* consumed_by_tx */ {consumed_supply}, /* tx */ *added_tx); 273 } 274 for (const auto& p : consumed_erased) { 275 Assert(outpoints_supply.erase(p) == 1); 276 Assert(outpoints_rbf.erase(p) == 1); 277 } 278 for (const auto& p : consumed_supply) { 279 Assert(outpoints_supply.erase(p) == 1); 280 } 281 } 282 } 283 Finish(fuzzed_data_provider, tx_pool, chainstate); 284 } 285 286 FUZZ_TARGET_INIT(tx_pool, initialize_tx_pool) 287 { 288 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 289 const auto& node = g_setup->m_node; 290 auto& chainstate = node.chainman->ActiveChainstate(); 291 292 MockTime(fuzzed_data_provider, chainstate); 293 SetMempoolConstraints(*node.args, fuzzed_data_provider); 294 295 std::vector<uint256> txids; 296 for (const auto& outpoint : g_outpoints_coinbase_init_mature) { 297 txids.push_back(outpoint.hash); 298 } 299 for (int i{0}; i <= 3; ++i) { 300 // Add some immature and non-existent outpoints 301 txids.push_back(g_outpoints_coinbase_init_immature.at(i).hash); 302 txids.push_back(ConsumeUInt256(fuzzed_data_provider)); 303 } 304 305 CTxMemPool tx_pool_{/* estimator */ nullptr, /* check_ratio */ 1}; 306 MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(&tx_pool_); 307 308 while (fuzzed_data_provider.ConsumeBool()) { 309 const auto mut_tx = ConsumeTransaction(fuzzed_data_provider, txids); 310 311 if (fuzzed_data_provider.ConsumeBool()) { 312 MockTime(fuzzed_data_provider, chainstate); 313 } 314 if (fuzzed_data_provider.ConsumeBool()) { 315 SetMempoolConstraints(*node.args, fuzzed_data_provider); 316 } 317 if (fuzzed_data_provider.ConsumeBool()) { 318 tx_pool.RollingFeeUpdate(); 319 } 320 if (fuzzed_data_provider.ConsumeBool()) { 321 const auto& txid = fuzzed_data_provider.ConsumeBool() ? 322 mut_tx.GetHash() : 323 PickValue(fuzzed_data_provider, txids); 324 const auto delta = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-50 * COIN, +50 * COIN); 325 tx_pool.PrioritiseTransaction(txid, delta); 326 } 327 328 const auto tx = MakeTransactionRef(mut_tx); 329 const bool bypass_limits = fuzzed_data_provider.ConsumeBool(); 330 ::fRequireStandard = fuzzed_data_provider.ConsumeBool(); 331 const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(node.chainman->ActiveChainstate(), tx_pool, tx, bypass_limits)); 332 const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID; 333 if (accepted) { 334 txids.push_back(tx->GetHash()); 335 } 336 } 337 Finish(fuzzed_data_provider, tx_pool, chainstate); 338 } 339 } // namespace 340