Coverage Report

Created: 2026-10-19 15:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/node/mempool_persist.cpp
Line
Count
Source
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
1.11k
{
45
1.11k
    if (load_path.empty()) return false;
46
47
1.03k
    AutoFile file{opts.mockable_fopen_function(load_path, "rb")};
48
1.03k
    if (file.IsNull()) {
49
520
        LogInfo("Failed to open mempool file. Continuing anyway.\n");
50
520
        return false;
51
520
    }
52
53
513
    int64_t count = 0;
54
513
    int64_t expired = 0;
55
513
    int64_t failed = 0;
56
513
    int64_t already_there = 0;
57
513
    int64_t unbroadcast = 0;
58
513
    const auto now{NodeClock::now()};
59
60
513
    try {
61
513
        uint64_t version;
62
513
        file >> version;
63
64
513
        if (version == MEMPOOL_DUMP_VERSION_NO_XOR_KEY) {
65
1
            file.SetObfuscation({});
66
512
        } else if (version == MEMPOOL_DUMP_VERSION) {
67
511
            Obfuscation obfuscation;
68
511
            file >> obfuscation;
69
511
            file.SetObfuscation(obfuscation);
70
511
        } else {
71
1
            return false;
72
1
        }
73
74
512
        uint64_t total_txns_to_load;
75
512
        file >> total_txns_to_load;
76
512
        uint64_t txns_tried = 0;
77
512
        LogInfo("Loading %u mempool transactions from file...\n", total_txns_to_load);
78
512
        int next_tenth_to_report = 0;
79
834
        while (txns_tried < total_txns_to_load) {
80
322
            const int percentage_done(100.0 * txns_tried / total_txns_to_load);
81
322
            if (next_tenth_to_report < percentage_done / 10) {
82
166
                LogInfo("Progress loading mempool transactions from file: %d%% (tried %u, %u remaining)\n",
83
166
                        percentage_done, txns_tried, total_txns_to_load - txns_tried);
84
166
                next_tenth_to_report = percentage_done / 10;
85
166
            }
86
322
            ++txns_tried;
87
88
322
            CTransactionRef tx;
89
322
            int64_t nTime;
90
322
            int64_t nFeeDelta;
91
322
            file >> TX_WITH_WITNESS(tx);
92
322
            file >> nTime;
93
322
            file >> nFeeDelta;
94
95
322
            if (opts.use_current_time) {
96
24
                nTime = TicksSinceEpoch<std::chrono::seconds>(now);
97
24
            }
98
99
322
            CAmount amountdelta = nFeeDelta;
100
322
            if (amountdelta && opts.apply_fee_delta_priority) {
101
16
                pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
102
16
            }
103
322
            if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_opts.expiry)) {
104
321
                LOCK(cs_main);
105
321
                const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false);
106
321
                if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) {
107
264
                    ++count;
108
264
                } 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
57
                    if (pool.exists(tx->GetHash())) {
114
36
                        ++already_there;
115
36
                    } else {
116
21
                        ++failed;
117
21
                    }
118
57
                }
119
321
            } else {
120
1
                ++expired;
121
1
            }
122
322
            if (active_chainstate.m_chainman.m_interrupt)
123
0
                return false;
124
322
        }
125
512
        std::map<Txid, CAmount> mapDeltas;
126
512
        file >> mapDeltas;
127
128
512
        if (opts.apply_fee_delta_priority) {
129
510
            for (const auto& i : mapDeltas) {
130
21
                pool.PrioritiseTransaction(i.first, i.second);
131
21
            }
132
510
        }
133
134
512
        std::set<Txid> unbroadcast_txids;
135
512
        file >> unbroadcast_txids;
136
512
        if (opts.apply_unbroadcast_set) {
137
509
            unbroadcast = unbroadcast_txids.size();
138
509
            for (const auto& txid : unbroadcast_txids) {
139
                // Ensure transactions were accepted to mempool then add to
140
                // unbroadcast set.
141
189
                if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid);
142
189
            }
143
509
        }
144
512
    } 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
511
    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
511
    return true;
151
513
}
152
153
bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mockable_fopen_function, bool skip_file_commit)
154
1.02k
{
155
1.02k
    auto start = SteadyClock::now();
156
157
1.02k
    std::map<Txid, CAmount> mapDeltas;
158
1.02k
    std::vector<TxMempoolInfo> vinfo;
159
1.02k
    std::set<Txid> unbroadcast_txids;
160
161
1.02k
    static Mutex dump_mutex;
162
1.02k
    LOCK(dump_mutex);
163
164
1.02k
    {
165
1.02k
        LOCK(pool.cs);
166
1.02k
        for (const auto &i : pool.mapDeltas) {
167
276
            mapDeltas[i.first] = i.second;
168
276
        }
169
1.02k
        vinfo = pool.infoAll();
170
1.02k
        unbroadcast_txids = pool.GetUnbroadcastTxs();
171
1.02k
    }
172
173
1.02k
    auto mid = SteadyClock::now();
174
175
1.02k
    const fs::path file_fspath{dump_path + ".new"};
176
1.02k
    AutoFile file{mockable_fopen_function(file_fspath, "wb")};
177
1.02k
    if (file.IsNull()) {
178
1
        return false;
179
1
    }
180
181
1.02k
    try {
182
1.02k
        file << MEMPOOL_DUMP_VERSION;
183
184
1.02k
        const Obfuscation obfuscation{FastRandomContext{}.randbytes<Obfuscation::KEY_SIZE>()};
185
1.02k
        file << obfuscation;
186
1.02k
        file.SetObfuscation(obfuscation);
187
188
1.02k
        uint64_t mempool_transactions_to_write(vinfo.size());
189
1.02k
        file << mempool_transactions_to_write;
190
1.02k
        LogInfo("Writing %u mempool transactions to file...\n", mempool_transactions_to_write);
191
1.23k
        for (const auto& i : vinfo) {
192
1.23k
            file << TX_WITH_WITNESS(*(i.tx));
193
1.23k
            file << int64_t{count_seconds(i.m_time)};
194
1.23k
            file << int64_t{i.nFeeDelta};
195
1.23k
            mapDeltas.erase(i.tx->GetHash());
196
1.23k
        }
197
198
1.02k
        file << mapDeltas;
199
200
1.02k
        LogInfo("Writing %d unbroadcast transactions to file.\n", unbroadcast_txids.size());
201
1.02k
        file << unbroadcast_txids;
202
203
1.02k
        if (!skip_file_commit && !file.Commit()) {
204
0
            (void)file.fclose();
205
0
            throw std::runtime_error("Commit failed");
206
0
        }
207
1.02k
        if (file.fclose() != 0) {
208
0
            throw std::runtime_error(
209
0
                strprintf("Error closing %s: %s", fs::PathToString(file_fspath), SysErrorString(errno)));
210
0
        }
211
1.02k
        if (!RenameOver(dump_path + ".new", dump_path)) {
212
0
            throw std::runtime_error("Rename failed");
213
0
        }
214
1.02k
        auto last = SteadyClock::now();
215
216
1.02k
        LogInfo("Dumped mempool: %.3fs to copy, %.3fs to dump, %d bytes dumped to file\n",
217
1.02k
                  Ticks<SecondsDouble>(mid - start),
218
1.02k
                  Ticks<SecondsDouble>(last - mid),
219
1.02k
                  fs::file_size(dump_path));
220
1.02k
    } catch (const std::exception& e) {
221
0
        LogInfo("Failed to dump mempool: %s. Continuing anyway.\n", e.what());
222
0
        (void)file.fclose();
223
0
        return false;
224
0
    }
225
1.02k
    return true;
226
1.02k
}
227
228
} // namespace node