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