mempool_persist.cpp
1 // Copyright (c) 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 <kernel/mempool_persist.h> 6 7 #include <clientversion.h> 8 #include <consensus/amount.h> 9 #include <logging.h> 10 #include <primitives/transaction.h> 11 #include <random.h> 12 #include <serialize.h> 13 #include <streams.h> 14 #include <sync.h> 15 #include <txmempool.h> 16 #include <uint256.h> 17 #include <util/fs.h> 18 #include <util/fs_helpers.h> 19 #include <util/signalinterrupt.h> 20 #include <util/time.h> 21 #include <validation.h> 22 23 #include <cstdint> 24 #include <cstdio> 25 #include <exception> 26 #include <functional> 27 #include <map> 28 #include <memory> 29 #include <set> 30 #include <stdexcept> 31 #include <utility> 32 #include <vector> 33 34 using fsbridge::FopenFn; 35 36 namespace kernel { 37 38 static const uint64_t MEMPOOL_DUMP_VERSION_NO_XOR_KEY{1}; 39 static const uint64_t MEMPOOL_DUMP_VERSION{2}; 40 41 bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, ImportMempoolOptions&& opts) 42 { 43 if (load_path.empty()) return false; 44 45 AutoFile file{opts.mockable_fopen_function(load_path, "rb")}; 46 if (file.IsNull()) { 47 LogPrintf("Failed to open mempool file from disk. Continuing anyway.\n"); 48 return false; 49 } 50 51 int64_t count = 0; 52 int64_t expired = 0; 53 int64_t failed = 0; 54 int64_t already_there = 0; 55 int64_t unbroadcast = 0; 56 const auto now{NodeClock::now()}; 57 58 try { 59 uint64_t version; 60 file >> version; 61 std::vector<std::byte> xor_key; 62 if (version == MEMPOOL_DUMP_VERSION_NO_XOR_KEY) { 63 // Leave XOR-key empty 64 } else if (version == MEMPOOL_DUMP_VERSION) { 65 file >> xor_key; 66 } else { 67 return false; 68 } 69 file.SetXor(xor_key); 70 uint64_t total_txns_to_load; 71 file >> total_txns_to_load; 72 uint64_t txns_tried = 0; 73 LogInfo("Loading %u mempool transactions from disk...\n", total_txns_to_load); 74 int next_tenth_to_report = 0; 75 while (txns_tried < total_txns_to_load) { 76 const int percentage_done(100.0 * txns_tried / total_txns_to_load); 77 if (next_tenth_to_report < percentage_done / 10) { 78 LogInfo("Progress loading mempool transactions from disk: %d%% (tried %u, %u remaining)\n", 79 percentage_done, txns_tried, total_txns_to_load - txns_tried); 80 next_tenth_to_report = percentage_done / 10; 81 } 82 ++txns_tried; 83 84 CTransactionRef tx; 85 int64_t nTime; 86 int64_t nFeeDelta; 87 file >> TX_WITH_WITNESS(tx); 88 file >> nTime; 89 file >> nFeeDelta; 90 91 if (opts.use_current_time) { 92 nTime = TicksSinceEpoch<std::chrono::seconds>(now); 93 } 94 95 CAmount amountdelta = nFeeDelta; 96 if (amountdelta && opts.apply_fee_delta_priority) { 97 pool.PrioritiseTransaction(tx->GetHash(), amountdelta); 98 } 99 if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_expiry)) { 100 LOCK(cs_main); 101 const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false); 102 if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) { 103 ++count; 104 } else { 105 // mempool may contain the transaction already, e.g. from 106 // wallet(s) having loaded it while we were processing 107 // mempool transactions; consider these as valid, instead of 108 // failed, but mark them as 'already there' 109 if (pool.exists(GenTxid::Txid(tx->GetHash()))) { 110 ++already_there; 111 } else { 112 ++failed; 113 } 114 } 115 } else { 116 ++expired; 117 } 118 if (active_chainstate.m_chainman.m_interrupt) 119 return false; 120 } 121 std::map<uint256, CAmount> mapDeltas; 122 file >> mapDeltas; 123 124 if (opts.apply_fee_delta_priority) { 125 for (const auto& i : mapDeltas) { 126 pool.PrioritiseTransaction(i.first, i.second); 127 } 128 } 129 130 std::set<uint256> unbroadcast_txids; 131 file >> unbroadcast_txids; 132 if (opts.apply_unbroadcast_set) { 133 unbroadcast = unbroadcast_txids.size(); 134 for (const auto& txid : unbroadcast_txids) { 135 // Ensure transactions were accepted to mempool then add to 136 // unbroadcast set. 137 if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid); 138 } 139 } 140 } catch (const std::exception& e) { 141 LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing anyway.\n", e.what()); 142 return false; 143 } 144 145 LogPrintf("Imported mempool transactions from disk: %i succeeded, %i failed, %i expired, %i already there, %i waiting for initial broadcast\n", count, failed, expired, already_there, unbroadcast); 146 return true; 147 } 148 149 bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mockable_fopen_function, bool skip_file_commit) 150 { 151 auto start = SteadyClock::now(); 152 153 std::map<uint256, CAmount> mapDeltas; 154 std::vector<TxMempoolInfo> vinfo; 155 std::set<uint256> unbroadcast_txids; 156 157 static Mutex dump_mutex; 158 LOCK(dump_mutex); 159 160 { 161 LOCK(pool.cs); 162 for (const auto &i : pool.mapDeltas) { 163 mapDeltas[i.first] = i.second; 164 } 165 vinfo = pool.infoAll(); 166 unbroadcast_txids = pool.GetUnbroadcastTxs(); 167 } 168 169 auto mid = SteadyClock::now(); 170 171 AutoFile file{mockable_fopen_function(dump_path + ".new", "wb")}; 172 if (file.IsNull()) { 173 return false; 174 } 175 176 try { 177 const uint64_t version{pool.m_persist_v1_dat ? MEMPOOL_DUMP_VERSION_NO_XOR_KEY : MEMPOOL_DUMP_VERSION}; 178 file << version; 179 180 std::vector<std::byte> xor_key(8); 181 if (!pool.m_persist_v1_dat) { 182 FastRandomContext{}.fillrand(xor_key); 183 file << xor_key; 184 } 185 file.SetXor(xor_key); 186 187 file << (uint64_t)vinfo.size(); 188 for (const auto& i : vinfo) { 189 file << TX_WITH_WITNESS(*(i.tx)); 190 file << int64_t{count_seconds(i.m_time)}; 191 file << int64_t{i.nFeeDelta}; 192 mapDeltas.erase(i.tx->GetHash()); 193 } 194 195 file << mapDeltas; 196 197 LogPrintf("Writing %d unbroadcast transactions to disk.\n", unbroadcast_txids.size()); 198 file << unbroadcast_txids; 199 200 if (!skip_file_commit && !FileCommit(file.Get())) 201 throw std::runtime_error("FileCommit failed"); 202 file.fclose(); 203 if (!RenameOver(dump_path + ".new", dump_path)) { 204 throw std::runtime_error("Rename failed"); 205 } 206 auto last = SteadyClock::now(); 207 208 LogPrintf("Dumped mempool: %.3fs to copy, %.3fs to dump\n", 209 Ticks<SecondsDouble>(mid - start), 210 Ticks<SecondsDouble>(last - mid)); 211 } catch (const std::exception& e) { 212 LogPrintf("Failed to dump mempool: %s. Continuing anyway.\n", e.what()); 213 return false; 214 } 215 return true; 216 } 217 218 } // namespace kernel