mempool_persist.cpp raw

   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 <primitives/transaction.h>
  10  #include <random.h>
  11  #include <serialize.h>
  12  #include <streams.h>
  13  #include <sync.h>
  14  #include <txmempool.h>
  15  #include <uint256.h>
  16  #include <util/fs.h>
  17  #include <util/fs_helpers.h>
  18  #include <util/log.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
 234