Coverage Report

Created: 2026-09-15 16:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/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);
21
0
    std::vector<WalletDescInfo> wallet_descriptors;
22
0
    for (const auto& spk_man : wallet.GetAllScriptPubKeyMans()) {
  Branch (22:30): [True: 0, False: 0]
23
0
        const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
24
0
        if (!desc_spk_man) {
  Branch (24:13): [True: 0, False: 0]
25
0
            return util::Unexpected{"Unexpected ScriptPubKey manager type."};
26
0
        }
27
0
        LOCK(desc_spk_man->cs_desc_man);
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))) {
  Branch (30:13): [True: 0, False: 0]
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.GetStart(), wallet_descriptor.GetEnd())) : std::nullopt,
  Branch (39:13): [True: 0, False: 0]
40
0
            wallet_descriptor.GetNext(),
41
0
            wallet_descriptor.cache
42
0
        );
43
0
    }
44
0
    return wallet_descriptors;
45
0
}
46
47
util::Result<std::string> ExportWatchOnlyWallet(const CWallet& wallet, const fs::path& destination, WalletContext& context)
48
0
{
49
0
    AssertLockHeld(wallet.cs_wallet);
50
51
0
    if (destination.empty()) {
  Branch (51:9): [True: 0, False: 0]
52
0
        return util::Error{_("Error: Export destination cannot be empty")};
53
0
    }
54
0
    if (fs::exists(destination)) {
  Branch (54:9): [True: 0, False: 0]
55
0
        return util::Error{strprintf(_("Error: Export destination '%s' already exists"), fs::PathToString(destination))};
56
0
    }
57
0
    if (!std::ofstream{fs::PathToString(destination)}) {
  Branch (57:9): [True: 0, False: 0]
58
0
        return util::Error{strprintf(_("Error: Could not create file '%s'"), fs::PathToString(destination))};
59
0
    }
60
0
    bool success = false;
61
0
    auto cleanup_destination = interfaces::MakeCleanupHandler([&success, &destination] {
62
0
        if (!success) fs::remove(destination);
  Branch (62:13): [True: 0, False: 0]
63
0
    });
64
65
    // Get the descriptors from this wallet
66
0
    util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(wallet, /*export_private=*/false);
67
0
    if (!exported) {
  Branch (67:9): [True: 0, False: 0]
68
0
        return util::Error{Untranslated(exported.error())};
69
0
    }
70
0
    if (exported->empty()) {
  Branch (70:9): [True: 0, False: 0]
71
0
        return util::Error{_("Error: Wallet has no descriptors to export")};
72
0
    }
73
74
    // Make the wallet with the same flags as this wallet, but without private keys
75
0
    const uint64_t create_flags = wallet.GetWalletFlags() | WALLET_FLAG_DISABLE_PRIVATE_KEYS;
76
77
    // Create the temporary watchonly wallet in memory to avoid leaving files on disk
78
0
    std::vector<bilingual_str> warnings;
79
0
    bilingual_str error;
80
0
    WalletContext empty_context;
81
0
    empty_context.args = context.args;
82
0
    std::shared_ptr<CWallet> watchonly_wallet = CWallet::CreateNew(empty_context, /*name=*/wallet.GetName() + "_watchonly_temp", MakeInMemoryWalletDatabase(), create_flags, /*born_encrypted=*/false, error, warnings);
83
0
    if (!watchonly_wallet) {
  Branch (83:9): [True: 0, False: 0]
84
0
        return util::Error{strprintf(_("Error: Failed to create new watchonly wallet. %s"), error)};
85
0
    }
86
87
0
    {
88
0
        LOCK(watchonly_wallet->cs_wallet);
89
90
        // Parse the descriptors and add them to the new wallet
91
0
        for (const WalletDescInfo& desc_info : *Assert(exported)) {
  Branch (91:46): [True: 0, False: 0]
92
            // Parse the descriptor
93
0
            FlatSigningProvider dummy_keys;
94
0
            std::string dummy_err;
95
0
            std::vector<std::unique_ptr<Descriptor>> descs = Parse(desc_info.descriptor, dummy_keys, dummy_err, /*require_checksum=*/true);
96
0
            CHECK_NONFATAL(descs.size() == 1); // All of our descriptors should be valid, and not multipath
97
0
            CHECK_NONFATAL(dummy_keys.keys.size() == 0); // No private keys should be present in our exported descriptors
98
99
            // Get the range if there is one
100
0
            int32_t range_start = 0;
101
0
            int32_t range_end = 0;
102
0
            if (desc_info.range) {
  Branch (102:17): [True: 0, False: 0]
103
0
                range_start = desc_info.range->first;
104
0
                range_end = desc_info.range->second;
105
0
            }
106
107
0
            WalletDescriptor w_desc(std::move(descs.at(0)), desc_info.creation_time, range_start, range_end, desc_info.next_index);
108
109
            // For descriptors that cannot self expand (i.e. needs private keys or cache), set the cache
110
0
            if (!w_desc.descriptor->CanSelfExpand()) {
  Branch (110:17): [True: 0, False: 0]
111
0
                w_desc.cache = desc_info.cache;
112
0
            }
113
114
            // Add to the watchonly wallet
115
0
            auto spkm_res = watchonly_wallet->AddWalletDescriptor(w_desc, dummy_keys, /*label=*/"", /*internal=*/false);
116
0
            if (!spkm_res) {
  Branch (116:17): [True: 0, False: 0]
117
0
                return util::Error{util::ErrorString(spkm_res)};
118
0
            }
119
120
            // Set active spkms as active
121
0
            if (desc_info.active) {
  Branch (121:17): [True: 0, False: 0]
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) {
  Branch (125:21): [True: 0, False: 0]
126
0
                    internal = *desc_info.internal;
127
0
                }
128
0
                watchonly_wallet->AddActiveScriptPubKeyMan(spkm_res->get().GetID(), *Assert(w_desc.descriptor->GetOutputType()), internal);
129
0
            }
130
0
        }
131
132
        // Copy locked coins that are persisted
133
0
        for (const auto& [coin, persisted] : wallet.m_locked_coins) {
  Branch (133:44): [True: 0, False: 0]
134
0
            if (!persisted) continue;
  Branch (134:17): [True: 0, False: 0]
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()) {
  Branch (141:17): [True: 0, False: 0]
142
0
                return util::Error{strprintf(_("Error: database transaction cannot be executed for new watchonly wallet %s"), watchonly_wallet->GetName())};
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)) {
  Branch (152:21): [True: 0, False: 0]
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)) {
  Branch (156:17): [True: 0, False: 0]
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) {
  Branch (161:42): [True: 0, False: 0]
162
0
                DataStream wtx_ser;
163
0
                wtx_ser << wtx;
164
0
                CWalletTx copy_wtx(deserialize, wtx_ser, wtx.GetTxs());
165
0
                if (!watchonly_wallet->LoadToWallet(std::move(copy_wtx))) {
  Branch (165:21): [True: 0, False: 0]
166
0
                    return util::Error{strprintf(_("Error: Could not add tx %s to watchonly wallet"), txid.GetHex())};
167
0
                }
168
0
                watchonly_batch.WriteFullTx(watchonly_wallet->mapWallet.at(txid));
169
0
            }
170
171
            // Copy address book
172
0
            for (const auto& [dest, entry] : wallet.m_address_book) {
  Branch (172:44): [True: 0, False: 0]
173
0
                auto address{EncodeDestination(dest)};
174
0
                if (entry.purpose) watchonly_batch.WritePurpose(address, PurposeToString(*entry.purpose));
  Branch (174:21): [True: 0, False: 0]
175
0
                if (entry.label) watchonly_batch.WriteName(address, *entry.label);
  Branch (175:21): [True: 0, False: 0]
176
0
                for (const auto& [id, request] : entry.receive_requests) {
  Branch (176:48): [True: 0, False: 0]
177
0
                    watchonly_batch.WriteAddressReceiveRequest(dest, id, request);
178
0
                }
179
0
                if (entry.previously_spent) watchonly_batch.WriteAddressPreviouslySpent(dest, true);
  Branch (179:21): [True: 0, False: 0]
180
0
            }
181
182
0
            if (!watchonly_batch.TxnCommit()) {
  Branch (182:17): [True: 0, False: 0]
183
0
                return util::Error{_("Error: cannot commit db transaction for watchonly wallet export")};
184
0
            }
185
0
        }
186
187
        // Make a backup of this wallet at the specified destination directory
188
0
        if (!watchonly_wallet->BackupWallet(fs::PathToString(destination))) {
  Branch (188:13): [True: 0, False: 0]
189
0
            return util::Error{_("Error: Unable to write the exported wallet")};
190
0
        }
191
0
        success = true;
192
0
    }
193
194
0
    return fs::PathToString(destination);
195
0
}
196
} // namespace wallet