/ src / node / mempool_persist.cpp
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