Branch data Line data Source code
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 <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 : 958 : bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, ImportMempoolOptions&& opts)
44 : : {
45 [ + + ]: 958 : if (load_path.empty()) return false;
46 : :
47 : 1802 : AutoFile file{opts.mockable_fopen_function(load_path, "rb")};
48 [ + + ]: 901 : if (file.IsNull()) {
49 [ + - ]: 467 : LogInfo("Failed to open mempool file. Continuing anyway.\n");
50 : : return false;
51 : : }
52 : :
53 : 434 : int64_t count = 0;
54 : 434 : int64_t expired = 0;
55 : 434 : int64_t failed = 0;
56 : 434 : int64_t already_there = 0;
57 : 434 : int64_t unbroadcast = 0;
58 : 434 : const auto now{NodeClock::now()};
59 : :
60 : 434 : try {
61 : 434 : uint64_t version;
62 [ + - ]: 434 : file >> version;
63 : :
64 [ + + ]: 434 : if (version == MEMPOOL_DUMP_VERSION_NO_XOR_KEY) {
65 : 2 : file.SetObfuscation({});
66 [ + - ]: 433 : } else if (version == MEMPOOL_DUMP_VERSION) {
67 : 433 : Obfuscation obfuscation;
68 [ + - ]: 433 : file >> obfuscation;
69 : 433 : file.SetObfuscation(obfuscation);
70 : : } else {
71 : : return false;
72 : : }
73 : :
74 : 434 : uint64_t total_txns_to_load;
75 [ + - ]: 434 : file >> total_txns_to_load;
76 : 434 : uint64_t txns_tried = 0;
77 [ + - ]: 434 : LogInfo("Loading %u mempool transactions from file...\n", total_txns_to_load);
78 : : int next_tenth_to_report = 0;
79 [ + + ]: 801 : while (txns_tried < total_txns_to_load) {
80 : 367 : const int percentage_done(100.0 * txns_tried / total_txns_to_load);
81 [ + + ]: 367 : if (next_tenth_to_report < percentage_done / 10) {
82 [ + - ]: 152 : 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 : 152 : next_tenth_to_report = percentage_done / 10;
85 : : }
86 : 367 : ++txns_tried;
87 : :
88 : 367 : CTransactionRef tx;
89 : 367 : int64_t nTime;
90 : 367 : int64_t nFeeDelta;
91 [ + - ]: 367 : file >> TX_WITH_WITNESS(tx);
92 [ + - ]: 367 : file >> nTime;
93 [ + - ]: 367 : file >> nFeeDelta;
94 : :
95 [ + + ]: 367 : if (opts.use_current_time) {
96 : 23 : nTime = TicksSinceEpoch<std::chrono::seconds>(now);
97 : : }
98 : :
99 : 367 : CAmount amountdelta = nFeeDelta;
100 [ + + + + ]: 367 : if (amountdelta && opts.apply_fee_delta_priority) {
101 [ + - ]: 16 : pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
102 : : }
103 [ + + ]: 367 : if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_opts.expiry)) {
104 [ + - ]: 366 : LOCK(cs_main);
105 [ + - ]: 366 : const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false);
106 [ + + ]: 366 : if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) {
107 : 322 : ++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 [ + - + + ]: 44 : if (pool.exists(tx->GetHash())) {
114 : 16 : ++already_there;
115 : : } else {
116 : 28 : ++failed;
117 : : }
118 : : }
119 [ + - ]: 732 : } else {
120 : 1 : ++expired;
121 : : }
122 [ + - - + ]: 367 : if (active_chainstate.m_chainman.m_interrupt)
123 [ # # ]: 0 : return false;
124 : 367 : }
125 [ + - ]: 434 : std::map<uint256, CAmount> mapDeltas;
126 [ + - ]: 434 : file >> mapDeltas;
127 : :
128 [ + + ]: 434 : if (opts.apply_fee_delta_priority) {
129 [ + + ]: 454 : for (const auto& i : mapDeltas) {
130 [ + - ]: 21 : pool.PrioritiseTransaction(i.first, i.second);
131 : : }
132 : : }
133 : :
134 [ + + ]: 434 : std::set<uint256> unbroadcast_txids;
135 [ + + ]: 434 : file >> unbroadcast_txids;
136 [ + + ]: 433 : if (opts.apply_unbroadcast_set) {
137 : 431 : unbroadcast = unbroadcast_txids.size();
138 [ + + ]: 680 : for (const auto& txid : unbroadcast_txids) {
139 : : // Ensure transactions were accepted to mempool then add to
140 : : // unbroadcast set.
141 [ + - + + : 483 : if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid);
+ + + - ]
142 : : }
143 : : }
144 [ - + ]: 435 : } catch (const std::exception& e) {
145 [ + - ]: 1 : LogInfo("Failed to deserialize mempool data on file: %s. Continuing anyway.\n", e.what());
146 : 1 : return false;
147 : 1 : }
148 : :
149 [ + - ]: 433 : 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 : 901 : }
152 : :
153 : 897 : bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mockable_fopen_function, bool skip_file_commit)
154 : : {
155 : 897 : auto start = SteadyClock::now();
156 : :
157 [ + + ]: 897 : std::map<uint256, CAmount> mapDeltas;
158 : 897 : std::vector<TxMempoolInfo> vinfo;
159 [ + + ]: 897 : std::set<uint256> unbroadcast_txids;
160 : :
161 [ + + + - ]: 897 : static Mutex dump_mutex;
162 [ + - ]: 897 : LOCK(dump_mutex);
163 : :
164 : 897 : {
165 [ + - ]: 897 : LOCK(pool.cs);
166 [ + + ]: 1172 : for (const auto &i : pool.mapDeltas) {
167 [ + - ]: 275 : mapDeltas[i.first] = i.second;
168 : : }
169 [ + - ]: 1794 : vinfo = pool.infoAll();
170 [ + - + - ]: 1794 : unbroadcast_txids = pool.GetUnbroadcastTxs();
171 : 0 : }
172 : :
173 : 897 : auto mid = SteadyClock::now();
174 : :
175 [ + - + - ]: 1794 : const fs::path file_fspath{dump_path + ".new"};
176 [ + - + - ]: 1794 : AutoFile file{mockable_fopen_function(file_fspath, "wb")};
177 [ + + ]: 897 : if (file.IsNull()) {
178 : : return false;
179 : : }
180 : :
181 : 896 : try {
182 [ + + ]: 896 : const uint64_t version{pool.m_opts.persist_v1_dat ? MEMPOOL_DUMP_VERSION_NO_XOR_KEY : MEMPOOL_DUMP_VERSION};
183 [ + - ]: 896 : file << version;
184 : :
185 [ + + ]: 896 : if (!pool.m_opts.persist_v1_dat) {
186 : 895 : const Obfuscation obfuscation{FastRandomContext{}.randbytes<Obfuscation::KEY_SIZE>()};
187 [ + - ]: 895 : file << obfuscation;
188 : 895 : file.SetObfuscation(obfuscation);
189 : : } else {
190 : 2 : file.SetObfuscation({});
191 : : }
192 : :
193 [ + - ]: 896 : uint64_t mempool_transactions_to_write(vinfo.size());
194 [ + - ]: 896 : file << mempool_transactions_to_write;
195 [ + - ]: 896 : LogInfo("Writing %u mempool transactions to file...\n", mempool_transactions_to_write);
196 [ + + ]: 2490 : for (const auto& i : vinfo) {
197 [ + - ]: 1594 : file << TX_WITH_WITNESS(*(i.tx));
198 [ + - ]: 1594 : file << int64_t{count_seconds(i.m_time)};
199 [ + - ]: 1594 : file << int64_t{i.nFeeDelta};
200 : 1594 : mapDeltas.erase(i.tx->GetHash());
201 : : }
202 : :
203 [ + - ]: 896 : file << mapDeltas;
204 : :
205 [ + - ]: 896 : LogInfo("Writing %d unbroadcast transactions to file.\n", unbroadcast_txids.size());
206 [ + - ]: 896 : file << unbroadcast_txids;
207 : :
208 [ + - + - : 896 : if (!skip_file_commit && !file.Commit()) {
- + ]
209 [ # # ]: 0 : (void)file.fclose();
210 [ # # ]: 0 : throw std::runtime_error("Commit failed");
211 : : }
212 [ + - - + ]: 1792 : if (file.fclose() != 0) {
213 : 0 : throw std::runtime_error(
214 [ # # # # : 0 : strprintf("Error closing %s: %s", fs::PathToString(file_fspath), SysErrorString(errno)));
# # ]
215 : : }
216 [ + - + - : 4480 : if (!RenameOver(dump_path + ".new", dump_path)) {
+ - + - -
+ ]
217 [ # # ]: 0 : throw std::runtime_error("Rename failed");
218 : : }
219 : 896 : auto last = SteadyClock::now();
220 : :
221 [ + - + - ]: 896 : 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 [ - - ]: 0 : } catch (const std::exception& e) {
226 [ - - ]: 0 : LogInfo("Failed to dump mempool: %s. Continuing anyway.\n", e.what());
227 [ - - ]: 0 : (void)file.fclose();
228 : 0 : return false;
229 : 0 : }
230 : : return true;
231 [ + - ]: 2691 : }
232 : :
233 : : } // namespace node
|