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