Coverage Report

Created: 2025-06-10 13:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/bitcoin/src/node/mempool_persist.cpp
Line
Count
Source
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
bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, ImportMempoolOptions&& opts)
42
11.0k
{
43
11.0k
    if (load_path.empty()) return false;
  Branch (43:9): [True: 0, False: 11.0k]
44
45
11.0k
    AutoFile file{opts.mockable_fopen_function(load_path, "rb")};
46
11.0k
    if (file.IsNull()) {
  Branch (46:9): [True: 11.0k, False: 0]
47
11.0k
        LogInfo("Failed to open mempool file. Continuing anyway.\n");
48
11.0k
        return false;
49
11.0k
    }
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) {
  Branch (62:13): [True: 0, False: 0]
63
            // Leave XOR-key empty
64
0
        } else if (version == MEMPOOL_DUMP_VERSION) {
  Branch (64:20): [True: 0, False: 0]
65
0
            file >> xor_key;
66
0
        } else {
67
0
            return false;
68
0
        }
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
0
        int next_tenth_to_report = 0;
75
0
        while (txns_tried < total_txns_to_load) {
  Branch (75:16): [True: 0, False: 0]
76
0
            const int percentage_done(100.0 * txns_tried / total_txns_to_load);
77
0
            if (next_tenth_to_report < percentage_done / 10) {
  Branch (77:17): [True: 0, False: 0]
78
0
                LogInfo("Progress loading mempool transactions from file: %d%% (tried %u, %u remaining)\n",
79
0
                        percentage_done, txns_tried, total_txns_to_load - txns_tried);
80
0
                next_tenth_to_report = percentage_done / 10;
81
0
            }
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) {
  Branch (91:17): [True: 0, False: 0]
92
0
                nTime = TicksSinceEpoch<std::chrono::seconds>(now);
93
0
            }
94
95
0
            CAmount amountdelta = nFeeDelta;
96
0
            if (amountdelta && opts.apply_fee_delta_priority) {
  Branch (96:17): [True: 0, False: 0]
  Branch (96:32): [True: 0, False: 0]
97
0
                pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
98
0
            }
99
0
            if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_opts.expiry)) {
  Branch (99:17): [True: 0, False: 0]
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) {
  Branch (102:21): [True: 0, False: 0]
103
0
                    ++count;
104
0
                } 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()))) {
  Branch (109:25): [True: 0, False: 0]
110
0
                        ++already_there;
111
0
                    } else {
112
0
                        ++failed;
113
0
                    }
114
0
                }
115
0
            } else {
116
0
                ++expired;
117
0
            }
118
0
            if (active_chainstate.m_chainman.m_interrupt)
  Branch (118:17): [True: 0, False: 0]
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) {
  Branch (124:13): [True: 0, False: 0]
125
0
            for (const auto& i : mapDeltas) {
  Branch (125:32): [True: 0, False: 0]
126
0
                pool.PrioritiseTransaction(i.first, i.second);
127
0
            }
128
0
        }
129
130
0
        std::set<uint256> unbroadcast_txids;
131
0
        file >> unbroadcast_txids;
132
0
        if (opts.apply_unbroadcast_set) {
  Branch (132:13): [True: 0, False: 0]
133
0
            unbroadcast = unbroadcast_txids.size();
134
0
            for (const auto& txid : unbroadcast_txids) {
  Branch (134:35): [True: 0, False: 0]
135
                // Ensure transactions were accepted to mempool then add to
136
                // unbroadcast set.
137
0
                if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid);
  Branch (137:21): [True: 0, False: 0]
138
0
            }
139
0
        }
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
0
    return true;
147
0
}
148
149
bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mockable_fopen_function, bool skip_file_commit)
150
11.0k
{
151
11.0k
    auto start = SteadyClock::now();
152
153
11.0k
    std::map<uint256, CAmount> mapDeltas;
154
11.0k
    std::vector<TxMempoolInfo> vinfo;
155
11.0k
    std::set<uint256> unbroadcast_txids;
156
157
11.0k
    static Mutex dump_mutex;
158
11.0k
    LOCK(dump_mutex);
159
160
11.0k
    {
161
11.0k
        LOCK(pool.cs);
162
11.0k
        for (const auto &i : pool.mapDeltas) {
  Branch (162:28): [True: 0, False: 11.0k]
163
0
            mapDeltas[i.first] = i.second;
164
0
        }
165
11.0k
        vinfo = pool.infoAll();
166
11.0k
        unbroadcast_txids = pool.GetUnbroadcastTxs();
167
11.0k
    }
168
169
11.0k
    auto mid = SteadyClock::now();
170
171
11.0k
    AutoFile file{mockable_fopen_function(dump_path + ".new", "wb")};
172
11.0k
    if (file.IsNull()) {
  Branch (172:9): [True: 0, False: 11.0k]
173
0
        return false;
174
0
    }
175
176
11.0k
    try {
177
11.0k
        const uint64_t version{pool.m_opts.persist_v1_dat ? MEMPOOL_DUMP_VERSION_NO_XOR_KEY : MEMPOOL_DUMP_VERSION};
  Branch (177:32): [True: 0, False: 11.0k]
178
11.0k
        file << version;
179
180
11.0k
        std::vector<std::byte> xor_key(8);
181
11.0k
        if (!pool.m_opts.persist_v1_dat) {
  Branch (181:13): [True: 11.0k, False: 0]
182
11.0k
            FastRandomContext{}.fillrand(xor_key);
183
11.0k
            file << xor_key;
184
11.0k
        }
185
11.0k
        file.SetXor(xor_key);
186
187
11.0k
        uint64_t mempool_transactions_to_write(vinfo.size());
188
11.0k
        file << mempool_transactions_to_write;
189
11.0k
        LogInfo("Writing %u mempool transactions to file...\n", mempool_transactions_to_write);
190
27.4k
        for (const auto& i : vinfo) {
  Branch (190:28): [True: 27.4k, False: 11.0k]
191
27.4k
            file << TX_WITH_WITNESS(*(i.tx));
192
27.4k
            file << int64_t{count_seconds(i.m_time)};
193
27.4k
            file << int64_t{i.nFeeDelta};
194
27.4k
            mapDeltas.erase(i.tx->GetHash());
195
27.4k
        }
196
197
11.0k
        file << mapDeltas;
198
199
11.0k
        LogInfo("Writing %d unbroadcast transactions to file.\n", unbroadcast_txids.size());
200
11.0k
        file << unbroadcast_txids;
201
202
11.0k
        if (!skip_file_commit && !file.Commit())
  Branch (202:13): [True: 11.0k, False: 0]
  Branch (202:34): [True: 0, False: 11.0k]
203
0
            throw std::runtime_error("Commit failed");
204
11.0k
        file.fclose();
205
11.0k
        if (!RenameOver(dump_path + ".new", dump_path)) {
  Branch (205:13): [True: 0, False: 11.0k]
206
0
            throw std::runtime_error("Rename failed");
207
0
        }
208
11.0k
        auto last = SteadyClock::now();
209
210
11.0k
        LogInfo("Dumped mempool: %.3fs to copy, %.3fs to dump, %d bytes dumped to file\n",
211
11.0k
                  Ticks<SecondsDouble>(mid - start),
212
11.0k
                  Ticks<SecondsDouble>(last - mid),
213
11.0k
                  fs::file_size(dump_path));
214
11.0k
    } 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
11.0k
    return true;
219
11.0k
}
220
221
} // namespace node