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/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 node {
37 : :
38 : : static const uint64_t MEMPOOL_DUMP_VERSION_NO_XOR_KEY{1};
39 : : static const uint64_t MEMPOOL_DUMP_VERSION{2};
40 : :
41 : 0 : bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, ImportMempoolOptions&& opts)
42 : : {
43 [ # # ]: 0 : if (load_path.empty()) return false;
44 : :
45 [ # # ]: 0 : AutoFile file{opts.mockable_fopen_function(load_path, "rb")};
46 [ # # ]: 0 : if (file.IsNull()) {
47 [ # # ]: 0 : LogInfo("Failed to open mempool file. Continuing anyway.\n");
48 : : return false;
49 : : }
50 : :
51 : 0 : int64_t count = 0;
52 : 0 : int64_t expired = 0;
53 : 0 : int64_t failed = 0;
54 : 0 : int64_t already_there = 0;
55 : 0 : int64_t unbroadcast = 0;
56 : 0 : const auto now{NodeClock::now()};
57 : :
58 : 0 : try {
59 : 0 : uint64_t version;
60 [ # # ]: 0 : file >> version;
61 : 0 : std::vector<std::byte> xor_key;
62 [ # # ]: 0 : if (version == MEMPOOL_DUMP_VERSION_NO_XOR_KEY) {
63 : : // Leave XOR-key empty
64 [ # # ]: 0 : } else if (version == MEMPOOL_DUMP_VERSION) {
65 [ # # ]: 0 : file >> xor_key;
66 : : } else {
67 : : return false;
68 : : }
69 [ # # ]: 0 : file.SetXor(xor_key);
70 : 0 : uint64_t total_txns_to_load;
71 [ # # ]: 0 : file >> total_txns_to_load;
72 : 0 : uint64_t txns_tried = 0;
73 [ # # ]: 0 : LogInfo("Loading %u mempool transactions from file...\n", total_txns_to_load);
74 : : int next_tenth_to_report = 0;
75 [ # # ]: 0 : while (txns_tried < total_txns_to_load) {
76 : 0 : const int percentage_done(100.0 * txns_tried / total_txns_to_load);
77 [ # # ]: 0 : if (next_tenth_to_report < percentage_done / 10) {
78 [ # # ]: 0 : LogInfo("Progress loading mempool transactions from file: %d%% (tried %u, %u remaining)\n",
79 : : percentage_done, txns_tried, total_txns_to_load - txns_tried);
80 : 0 : next_tenth_to_report = percentage_done / 10;
81 : : }
82 : 0 : ++txns_tried;
83 : :
84 : 0 : CTransactionRef tx;
85 : 0 : int64_t nTime;
86 : 0 : int64_t nFeeDelta;
87 [ # # ]: 0 : file >> TX_WITH_WITNESS(tx);
88 [ # # ]: 0 : file >> nTime;
89 [ # # ]: 0 : file >> nFeeDelta;
90 : :
91 [ # # ]: 0 : if (opts.use_current_time) {
92 : 0 : nTime = TicksSinceEpoch<std::chrono::seconds>(now);
93 : : }
94 : :
95 : 0 : CAmount amountdelta = nFeeDelta;
96 [ # # # # ]: 0 : if (amountdelta && opts.apply_fee_delta_priority) {
97 [ # # ]: 0 : pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
98 : : }
99 [ # # ]: 0 : if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_opts.expiry)) {
100 [ # # ]: 0 : LOCK(cs_main);
101 [ # # ]: 0 : const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false);
102 [ # # ]: 0 : if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) {
103 : 0 : ++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 [ # # # # ]: 0 : if (pool.exists(GenTxid::Txid(tx->GetHash()))) {
110 : 0 : ++already_there;
111 : : } else {
112 : 0 : ++failed;
113 : : }
114 : : }
115 [ # # ]: 0 : } else {
116 : 0 : ++expired;
117 : : }
118 [ # # # # ]: 0 : if (active_chainstate.m_chainman.m_interrupt)
119 [ # # ]: 0 : return false;
120 : 0 : }
121 [ # # ]: 0 : std::map<uint256, CAmount> mapDeltas;
122 [ # # ]: 0 : file >> mapDeltas;
123 : :
124 [ # # ]: 0 : if (opts.apply_fee_delta_priority) {
125 [ # # ]: 0 : for (const auto& i : mapDeltas) {
126 [ # # ]: 0 : pool.PrioritiseTransaction(i.first, i.second);
127 : : }
128 : : }
129 : :
130 [ # # ]: 0 : std::set<uint256> unbroadcast_txids;
131 [ # # ]: 0 : file >> unbroadcast_txids;
132 [ # # ]: 0 : if (opts.apply_unbroadcast_set) {
133 : 0 : unbroadcast = unbroadcast_txids.size();
134 [ # # ]: 0 : for (const auto& txid : unbroadcast_txids) {
135 : : // Ensure transactions were accepted to mempool then add to
136 : : // unbroadcast set.
137 [ # # # # : 0 : if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid);
# # # # ]
138 : : }
139 : : }
140 [ # # ]: 0 : } catch (const std::exception& e) {
141 [ - - ]: 0 : LogInfo("Failed to deserialize mempool data on file: %s. Continuing anyway.\n", e.what());
142 : 0 : return false;
143 : 0 : }
144 : :
145 [ # # ]: 0 : 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);
146 : : return true;
147 : 0 : }
148 : :
149 : 0 : bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mockable_fopen_function, bool skip_file_commit)
150 : : {
151 : 0 : auto start = SteadyClock::now();
152 : :
153 [ # # ]: 0 : std::map<uint256, CAmount> mapDeltas;
154 : 0 : std::vector<TxMempoolInfo> vinfo;
155 [ # # ]: 0 : std::set<uint256> unbroadcast_txids;
156 : :
157 [ # # # # ]: 0 : static Mutex dump_mutex;
158 [ # # ]: 0 : LOCK(dump_mutex);
159 : :
160 : 0 : {
161 [ # # ]: 0 : LOCK(pool.cs);
162 [ # # ]: 0 : for (const auto &i : pool.mapDeltas) {
163 [ # # ]: 0 : mapDeltas[i.first] = i.second;
164 : : }
165 [ # # ]: 0 : vinfo = pool.infoAll();
166 [ # # # # ]: 0 : unbroadcast_txids = pool.GetUnbroadcastTxs();
167 : 0 : }
168 : :
169 : 0 : auto mid = SteadyClock::now();
170 : :
171 [ # # # # : 0 : AutoFile file{mockable_fopen_function(dump_path + ".new", "wb")};
# # # # ]
172 [ # # ]: 0 : if (file.IsNull()) {
173 : : return false;
174 : : }
175 : :
176 : 0 : try {
177 [ # # ]: 0 : const uint64_t version{pool.m_opts.persist_v1_dat ? MEMPOOL_DUMP_VERSION_NO_XOR_KEY : MEMPOOL_DUMP_VERSION};
178 [ # # ]: 0 : file << version;
179 : :
180 [ # # ]: 0 : std::vector<std::byte> xor_key(8);
181 [ # # ]: 0 : if (!pool.m_opts.persist_v1_dat) {
182 : 0 : FastRandomContext{}.fillrand(xor_key);
183 [ # # ]: 0 : file << xor_key;
184 : : }
185 [ # # ]: 0 : file.SetXor(xor_key);
186 : :
187 [ # # ]: 0 : uint64_t mempool_transactions_to_write(vinfo.size());
188 [ # # ]: 0 : file << mempool_transactions_to_write;
189 [ # # ]: 0 : LogInfo("Writing %u mempool transactions to file...\n", mempool_transactions_to_write);
190 [ # # ]: 0 : for (const auto& i : vinfo) {
191 [ # # ]: 0 : file << TX_WITH_WITNESS(*(i.tx));
192 [ # # ]: 0 : file << int64_t{count_seconds(i.m_time)};
193 [ # # ]: 0 : file << int64_t{i.nFeeDelta};
194 : 0 : mapDeltas.erase(i.tx->GetHash());
195 : : }
196 : :
197 [ # # ]: 0 : file << mapDeltas;
198 : :
199 [ # # ]: 0 : LogInfo("Writing %d unbroadcast transactions to file.\n", unbroadcast_txids.size());
200 [ # # ]: 0 : file << unbroadcast_txids;
201 : :
202 [ # # # # : 0 : if (!skip_file_commit && !file.Commit())
# # ]
203 [ # # ]: 0 : throw std::runtime_error("Commit failed");
204 [ # # ]: 0 : file.fclose();
205 [ # # # # : 0 : if (!RenameOver(dump_path + ".new", dump_path)) {
# # # # #
# ]
206 [ # # ]: 0 : throw std::runtime_error("Rename failed");
207 : : }
208 : 0 : auto last = SteadyClock::now();
209 : :
210 [ # # # # ]: 0 : LogInfo("Dumped mempool: %.3fs to copy, %.3fs to dump, %d bytes dumped to file\n",
211 : : Ticks<SecondsDouble>(mid - start),
212 : : Ticks<SecondsDouble>(last - mid),
213 : : fs::file_size(dump_path));
214 [ # # ]: 0 : } catch (const std::exception& e) {
215 [ - - ]: 0 : LogInfo("Failed to dump mempool: %s. Continuing anyway.\n", e.what());
216 : 0 : return false;
217 : 0 : }
218 : 0 : return true;
219 [ # # ]: 0 : }
220 : :
221 : : } // namespace node
|