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