bitcoin core ipc fuzz coverage

Coverage Report

Created: 2026-08-26 16:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/enochazariah/dev/bitcoin-invariants/src/wallet/export.cpp
Line
Count
Source
1
// Copyright (c) 2026-present The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
4
5
#include <wallet/export.h>
6
7
#include <key_io.h>
8
#include <util/fs.h>
9
#include <util/expected.h>
10
#include <wallet/scriptpubkeyman.h>
11
#include <wallet/context.h>
12
#include <wallet/sqlite.h>
13
#include <wallet/wallet.h>
14
15
#include <fstream>
16
17
namespace wallet {
18
util::Expected<std::vector<WalletDescInfo>, std::string> ExportDescriptors(const CWallet& wallet, bool export_private)
19
0
{
20
0
    AssertLockHeld(wallet.cs_wallet);
Line
Count
Source
144
0
#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
21
0
    std::vector<WalletDescInfo> wallet_descriptors;
22
0
    for (const auto& spk_man : wallet.GetAllScriptPubKeyMans()) {
23
0
        const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
24
0
        if (!desc_spk_man) {
25
0
            return util::Unexpected{"Unexpected ScriptPubKey manager type."};
26
0
        }
27
0
        LOCK(desc_spk_man->cs_desc_man);
Line
Count
Source
268
0
#define LOCK(cs) UniqueLock BITCOIN_UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
Line
Count
Source
11
0
#define BITCOIN_UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
Line
Count
Source
9
0
#define PASTE2(x, y) PASTE(x, y)
Line
Count
Source
8
0
#define PASTE(x, y) x ## y
28
0
        const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
29
0
        std::string descriptor;
30
0
        if (!Assume(desc_spk_man->GetDescriptorString(descriptor, export_private))) {
Line
Count
Source
128
0
#define Assume(val) inline_assertion_check<false>(val, std::source_location::current(), #val)
31
0
            return util::Unexpected{"Can't get descriptor string."};
32
0
        }
33
0
        const bool is_range = wallet_descriptor.descriptor->IsRange();
34
0
        wallet_descriptors.emplace_back(
35
0
            descriptor,
36
0
            wallet_descriptor.creation_time,
37
0
            wallet.IsActiveScriptPubKeyMan(*desc_spk_man),
38
0
            wallet.IsInternalScriptPubKeyMan(desc_spk_man),
39
0
            is_range ? std::optional(std::make_pair(wallet_descriptor.range_start, wallet_descriptor.range_end)) : std::nullopt,
40
0
            wallet_descriptor.next_index
41
0
        );
42
0
    }
43
0
    return wallet_descriptors;
44
0
}
45
46
util::Result<std::string> ExportWatchOnlyWallet(const CWallet& wallet, const fs::path& destination, WalletContext& context)
47
0
{
48
0
    AssertLockHeld(wallet.cs_wallet);
Line
Count
Source
144
0
#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
49
50
0
    if (destination.empty()) {
51
0
        return util::Error{_("Error: Export destination cannot be empty")};
52
0
    }
53
0
    if (fs::exists(destination)) {
54
0
        return util::Error{strprintf(_("Error: Export destination '%s' already exists"), fs::PathToString(destination))};
Line
Count
Source
1172
0
#define strprintf tfm::format
55
0
    }
56
0
    if (!std::ofstream{fs::PathToString(destination)}) {
57
0
        return util::Error{strprintf(_("Error: Could not create file '%s'"), fs::PathToString(destination))};
Line
Count
Source
1172
0
#define strprintf tfm::format
58
0
    }
59
0
    bool success = false;
60
0
    auto cleanup_destination = interfaces::MakeCleanupHandler([&success, &destination] {
61
0
        if (!success) fs::remove(destination);
62
0
    });
63
64
    // Get the descriptors from this wallet
65
0
    util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(wallet, /*export_private=*/false);
66
0
    if (!exported) {
67
0
        return util::Error{Untranslated(exported.error())};
68
0
    }
69
0
    if (exported->empty()) {
70
0
        return util::Error{_("Error: Wallet has no descriptors to export")};
71
0
    }
72
73
    // Make the wallet with the same flags as this wallet, but without private keys
74
0
    const uint64_t create_flags = wallet.GetWalletFlags() | WALLET_FLAG_DISABLE_PRIVATE_KEYS;
75
76
    // Create the temporary watchonly wallet in memory to avoid leaving files on disk
77
0
    std::vector<bilingual_str> warnings;
78
0
    bilingual_str error;
79
0
    WalletContext empty_context;
80
0
    empty_context.args = context.args;
81
0
    std::shared_ptr<CWallet> watchonly_wallet = CWallet::CreateNew(empty_context, /*name=*/wallet.GetName() + "_watchonly_temp", MakeInMemoryWalletDatabase(), create_flags, /*born_encrypted=*/false, error, warnings);
82
0
    if (!watchonly_wallet) {
83
0
        return util::Error{strprintf(_("Error: Failed to create new watchonly wallet. %s"), error)};
Line
Count
Source
1172
0
#define strprintf tfm::format
84
0
    }
85
86
0
    {
87
0
        LOCK(watchonly_wallet->cs_wallet);
Line
Count
Source
268
0
#define LOCK(cs) UniqueLock BITCOIN_UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
Line
Count
Source
11
0
#define BITCOIN_UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
Line
Count
Source
9
0
#define PASTE2(x, y) PASTE(x, y)
Line
Count
Source
8
0
#define PASTE(x, y) x ## y
88
89
        // Parse the descriptors and add them to the new wallet
90
0
        for (const WalletDescInfo& desc_info : *Assert(exported)) {
Line
Count
Source
116
0
#define Assert(val) inline_assertion_check<true>(val, std::source_location::current(), #val)
91
            // Parse the descriptor
92
0
            FlatSigningProvider dummy_keys;
93
0
            std::string dummy_err;
94
0
            std::vector<std::unique_ptr<Descriptor>> descs = Parse(desc_info.descriptor, dummy_keys, dummy_err, /*require_checksum=*/true);
95
0
            CHECK_NONFATAL(descs.size() == 1); // All of our descriptors should be valid, and not multipath
Line
Count
Source
113
0
    inline_check_non_fatal(condition, std::source_location::current(), #condition)
96
0
            CHECK_NONFATAL(dummy_keys.keys.size() == 0); // No private keys should be present in our exported descriptors
Line
Count
Source
113
0
    inline_check_non_fatal(condition, std::source_location::current(), #condition)
97
98
            // Get the range if there is one
99
0
            int32_t range_start = 0;
100
0
            int32_t range_end = 0;
101
0
            if (desc_info.range) {
102
0
                range_start = desc_info.range->first;
103
0
                range_end = desc_info.range->second;
104
0
            }
105
106
0
            WalletDescriptor w_desc(std::move(descs.at(0)), desc_info.creation_time, range_start, range_end, desc_info.next_index);
107
108
            // For descriptors that cannot self expand (i.e. needs private keys or cache), retrieve the cache
109
0
            uint256 desc_id = w_desc.id;
110
0
            if (!w_desc.descriptor->CanSelfExpand()) {
111
0
                DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(wallet.GetScriptPubKeyMan(desc_id));
112
0
                w_desc.cache = WITH_LOCK(desc_spkm->cs_desc_man, return desc_spkm->GetWalletDescriptor().cache);
Line
Count
Source
299
0
#define WITH_LOCK(cs, code) (MaybeCheckNotHeld(cs), [&]() -> decltype(auto) { LOCK(cs); code; }())
113
0
            }
114
115
            // Add to the watchonly wallet
116
0
            if (auto spkm_res = watchonly_wallet->AddWalletDescriptor(w_desc, dummy_keys, /*label=*/"", /*internal=*/false); !spkm_res) {
117
0
                return util::Error{util::ErrorString(spkm_res)};
118
0
            }
119
120
            // Set active spkms as active
121
0
            if (desc_info.active) {
122
                // Determine whether this descriptor is internal
123
                // This is only set for active spkms
124
0
                bool internal = false;
125
0
                if (desc_info.internal) {
126
0
                    internal = *desc_info.internal;
127
0
                }
128
0
                watchonly_wallet->AddActiveScriptPubKeyMan(desc_id, *Assert(w_desc.descriptor->GetOutputType()), internal);
Line
Count
Source
116
0
#define Assert(val) inline_assertion_check<true>(val, std::source_location::current(), #val)
129
0
            }
130
0
        }
131
132
        // Copy locked coins that are persisted
133
0
        for (const auto& [coin, persisted] : wallet.m_locked_coins) {
134
0
            if (!persisted) continue;
135
0
            watchonly_wallet->LockCoin(coin, persisted);
136
0
        }
137
138
0
        {
139
            // Make a WalletBatch for the watchonly wallet so that everything else can be written atomically
140
0
            WalletBatch watchonly_batch(watchonly_wallet->GetDatabase());
141
0
            if (!watchonly_batch.TxnBegin()) {
142
0
                return util::Error{strprintf(_("Error: database transaction cannot be executed for new watchonly wallet %s"), watchonly_wallet->GetName())};
Line
Count
Source
1172
0
#define strprintf tfm::format
143
0
            }
144
145
            // Copy orderPosNext
146
0
            watchonly_batch.WriteOrderPosNext(wallet.nOrderPosNext);
147
148
            // Write the best block locator to avoid rescanning on reload
149
0
            CBlockLocator best_block_locator;
150
0
            {
151
0
                WalletBatch local_wallet_batch(wallet.GetDatabase());
152
0
                if (!local_wallet_batch.ReadBestBlock(best_block_locator)) {
153
0
                    return util::Error{_("Error: Unable to read wallet's best block locator record")};
154
0
                }
155
0
            }
156
0
            if (!watchonly_batch.WriteBestBlock(best_block_locator)) {
157
0
                return util::Error{_("Error: Unable to write watchonly wallet best block locator record")};
158
0
            }
159
160
            // Copy the transactions
161
0
            for (const auto& [txid, wtx] : wallet.mapWallet) {
162
0
                if (!watchonly_wallet->LoadToWallet(txid, [&](CWalletTx& ins_wtx, bool new_tx) EXCLUSIVE_LOCKS_REQUIRED(watchonly_wallet->cs_wallet) {
163
0
                    if (!new_tx) return false;
164
0
                    ins_wtx.SetTx(wtx.tx);
165
0
                    ins_wtx.CopyFrom(wtx);
166
0
                    return true;
167
0
                })) {
168
0
                    return util::Error{strprintf(_("Error: Could not add tx %s to watchonly wallet"), txid.GetHex())};
Line
Count
Source
1172
0
#define strprintf tfm::format
169
0
                }
170
0
                watchonly_batch.WriteTx(watchonly_wallet->mapWallet.at(txid));
171
0
            }
172
173
            // Copy address book
174
0
            for (const auto& [dest, entry] : wallet.m_address_book) {
175
0
                auto address{EncodeDestination(dest)};
176
0
                if (entry.purpose) watchonly_batch.WritePurpose(address, PurposeToString(*entry.purpose));
177
0
                if (entry.label) watchonly_batch.WriteName(address, *entry.label);
178
0
                for (const auto& [id, request] : entry.receive_requests) {
179
0
                    watchonly_batch.WriteAddressReceiveRequest(dest, id, request);
180
0
                }
181
0
                if (entry.previously_spent) watchonly_batch.WriteAddressPreviouslySpent(dest, true);
182
0
            }
183
184
0
            if (!watchonly_batch.TxnCommit()) {
185
0
                return util::Error{_("Error: cannot commit db transaction for watchonly wallet export")};
186
0
            }
187
0
        }
188
189
        // Make a backup of this wallet at the specified destination directory
190
0
        if (!watchonly_wallet->BackupWallet(fs::PathToString(destination))) {
191
0
            return util::Error{_("Error: Unable to write the exported wallet")};
192
0
        }
193
0
        success = true;
194
0
    }
195
196
0
    return fs::PathToString(destination);
197
0
}
198
} // namespace wallet