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