wallettool.cpp raw
1 // Copyright (c) 2016-2022 The Limenka 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 <limenka-build-config.h> // IWYU pragma: keep
6
7 #include <wallet/wallettool.h>
8
9 #include <common/args.h>
10 #include <tinyformat.h>
11 #include <univalue.h>
12 #include <util/fs.h>
13 #include <util/translation.h>
14 #include <wallet/dump.h>
15 #include <wallet/salvage.h>
16 #include <wallet/wallet.h>
17 #include <wallet/walletutil.h>
18
19 #include <cassert>
20 #include <fstream>
21 #include <string>
22
23 namespace wallet {
24
25 UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp, const std::vector<CExtKey>& master_keys = {})
26 EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
27
28 namespace WalletTool {
29
30 // The standard wallet deleter function blocks on the validation interface
31 // queue, which doesn't exist for the limenka-wallet. Define our own
32 // deleter here.
33 static void WalletToolReleaseWallet(CWallet* wallet)
34 {
35 wallet->WalletLogPrintf("Releasing wallet\n");
36 wallet->Close();
37 delete wallet;
38 }
39
40 static void WalletCreate(CWallet* wallet_instance, uint64_t wallet_creation_flags)
41 {
42 LOCK(wallet_instance->cs_wallet);
43
44 wallet_instance->SetMinVersion(FEATURE_LATEST);
45 wallet_instance->InitWalletFlags(wallet_creation_flags);
46
47 if (wallet_instance->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) {
48 return;
49 }
50
51 if (!wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
52 auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan();
53 spk_man->SetupGeneration(false);
54 } else {
55 wallet_instance->SetupDescriptorScriptPubKeyMans();
56 }
57
58 tfm::format(std::cout, "Topping up keypool...\n");
59 wallet_instance->TopUpKeyPool();
60 }
61
62 static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, DatabaseOptions options, CWallet::do_init_used_flag do_init_used_flag_val = CWallet::do_init_used_flag::Init)
63 {
64 DatabaseStatus status;
65 bilingual_str error;
66 std::unique_ptr<WalletDatabase> database = MakeDatabase(path, options, status, error);
67 if (!database) {
68 tfm::format(std::cerr, "%s\n", error.original);
69 return nullptr;
70 }
71
72 // dummy chain interface
73 std::shared_ptr<CWallet> wallet_instance{new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet};
74 DBErrors load_wallet_ret;
75 try {
76 load_wallet_ret = wallet_instance->LoadWallet(do_init_used_flag_val);
77 } catch (const std::runtime_error&) {
78 tfm::format(std::cerr, "Error loading %s. Is wallet being used by another process?\n", name);
79 return nullptr;
80 }
81
82 if (load_wallet_ret != DBErrors::LOAD_OK) {
83 if (load_wallet_ret == DBErrors::CORRUPT) {
84 tfm::format(std::cerr, "Error loading %s: Wallet corrupted", name);
85 return nullptr;
86 } else if (load_wallet_ret == DBErrors::NONCRITICAL_ERROR) {
87 tfm::format(std::cerr, "Error reading %s! All keys read correctly, but transaction data"
88 " or address book entries might be missing or incorrect.",
89 name);
90 } else if (load_wallet_ret == DBErrors::TOO_NEW) {
91 tfm::format(std::cerr, "Error loading %s: Wallet requires newer version of %s",
92 name, CLIENT_NAME);
93 return nullptr;
94 } else if (load_wallet_ret == DBErrors::NEED_REWRITE) {
95 tfm::format(std::cerr, "Wallet needed to be rewritten: restart %s to complete", CLIENT_NAME);
96 return nullptr;
97 } else if (load_wallet_ret == DBErrors::NEED_RESCAN) {
98 tfm::format(std::cerr, "Error reading %s! Some transaction data might be missing or"
99 " incorrect. Wallet requires a rescan.",
100 name);
101 } else {
102 tfm::format(std::cerr, "Error loading %s", name);
103 return nullptr;
104 }
105 }
106
107 if (options.require_create) WalletCreate(wallet_instance.get(), options.create_flags);
108
109 return wallet_instance;
110 }
111
112 static void WalletShowInfo(CWallet* wallet_instance)
113 {
114 LOCK(wallet_instance->cs_wallet);
115
116 tfm::format(std::cout, "Wallet info\n===========\n");
117 tfm::format(std::cout, "Name: %s\n", wallet_instance->GetName());
118 tfm::format(std::cout, "Format: %s\n", wallet_instance->GetDatabase().Format());
119 tfm::format(std::cout, "Descriptors: %s\n", wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? "yes" : "no");
120 tfm::format(std::cout, "Encrypted: %s\n", wallet_instance->IsCrypted() ? "yes" : "no");
121 tfm::format(std::cout, "HD (hd seed available): %s\n", wallet_instance->IsHDEnabled() ? "yes" : "no");
122 tfm::format(std::cout, "Keypool Size: %u\n", wallet_instance->GetKeyPoolSize());
123 tfm::format(std::cout, "Transactions: %zu\n", wallet_instance->mapWallet.size());
124 tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->m_address_book.size());
125 }
126
127 static bool ReadAndParseColdcardFile(const fs::path& path, UniValue& decriptors)
128 {
129 std::ifstream file;
130 file.open(path);
131 if (!file.is_open()) {
132 tfm::format(std::cerr, "%s. Please check permissions.\n", fs::PathToString(path));
133 return false;
134 }
135
136 std::string line;
137 while (std::getline(file, line)) {
138 if (line.substr(0, 22) == "importdescriptors \'[{\"") break;
139 }
140
141 file.close();
142
143 decriptors.clear();
144 if (!decriptors.read(line.substr(19, line.size() - 20))) {
145 tfm::format(std::cerr, "Unable to parse %s\n", fs::PathToString(path));
146 return false;
147 }
148
149 assert(decriptors.isArray());
150 return true;
151 }
152
153 bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
154 {
155 if (args.IsArgSet("-format") && command != "createfromdump") {
156 tfm::format(std::cerr, "The -format option can only be used with the \"createfromdump\" command.\n");
157 return false;
158 }
159 if (args.IsArgSet("-dumpfile") && command != "dump" && command != "createfromdump" && command != "importfromcoldcard") {
160 tfm::format(std::cerr, "The -dumpfile option can only be used with the \"dump\", \"createfromdump\" and \"importfromcoldcard\" commands.\n");
161 return false;
162 }
163 if (args.IsArgSet("-descriptors") && command != "create") {
164 tfm::format(std::cerr, "The -descriptors option can only be used with the 'create' command.\n");
165 return false;
166 }
167 if (args.IsArgSet("-legacy") && command != "create") {
168 tfm::format(std::cerr, "The -legacy option can only be used with the 'create' command.\n");
169 return false;
170 }
171 if (command == "create" && !args.IsArgSet("-wallet")) {
172 tfm::format(std::cerr, "Wallet name must be provided when creating a new wallet.\n");
173 return false;
174 }
175 const std::string name = args.GetArg("-wallet", "");
176 const fs::path path = fsbridge::AbsPathJoin(GetWalletDir(), fs::PathFromString(name));
177
178 if (command == "create") {
179 DatabaseOptions options;
180 ReadDatabaseArgs(args, options);
181 options.require_create = true;
182 // If -legacy is set, use it. Otherwise default to false.
183 bool make_legacy = args.GetBoolArg("-legacy", false);
184 // If neither -legacy nor -descriptors is set, default to true. If -descriptors is set, use its value.
185 bool make_descriptors = (!args.IsArgSet("-descriptors") && !args.IsArgSet("-legacy")) || (args.IsArgSet("-descriptors") && args.GetBoolArg("-descriptors", true));
186 if (make_legacy && make_descriptors) {
187 tfm::format(std::cerr, "Only one of -legacy or -descriptors can be set to true, not both\n");
188 return false;
189 }
190 if (!make_legacy && !make_descriptors) {
191 tfm::format(std::cerr, "One of -legacy or -descriptors must be set to true (or omitted)\n");
192 return false;
193 }
194 if (make_descriptors) {
195 options.create_flags |= WALLET_FLAG_DESCRIPTORS;
196 options.require_format = DatabaseFormat::SQLITE;
197 }
198
199 const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
200 if (wallet_instance) {
201 WalletShowInfo(wallet_instance.get());
202 wallet_instance->Close();
203 }
204 } else if (command == "info") {
205 DatabaseOptions options;
206 ReadDatabaseArgs(args, options);
207 options.require_existing = true;
208 // NOTE: We need to skip initialisation of the m_used flag, or else the address book count might be wrong
209 const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options, CWallet::do_init_used_flag::Skip);
210 if (!wallet_instance) return false;
211 WalletShowInfo(wallet_instance.get());
212 wallet_instance->Close();
213 } else if (command == "salvage") {
214 #ifdef USE_BDB
215 bilingual_str error;
216 std::vector<bilingual_str> warnings;
217 bool ret = RecoverDatabaseFile(args, path, error, warnings);
218 if (!ret) {
219 for (const auto& warning : warnings) {
220 tfm::format(std::cerr, "%s\n", warning.original);
221 }
222 if (!error.empty()) {
223 tfm::format(std::cerr, "%s\n", error.original);
224 }
225 }
226 return ret;
227 #else
228 tfm::format(std::cerr, "Salvage command is not available as BDB support is not compiled");
229 return false;
230 #endif
231 } else if (command == "dump") {
232 DatabaseOptions options;
233 ReadDatabaseArgs(args, options);
234 options.require_existing = true;
235
236 // Get the dumpfile
237 std::string dump_filename = args.GetArg("-dumpfile", "");
238 if (dump_filename.empty()) {
239 tfm::format(std::cerr, "No dump file provided. To use dump, -dumpfile=<filename> must be provided.\n");
240 return false;
241 }
242
243 DatabaseStatus status;
244
245 if (args.GetBoolArg("-withinternalbdb", false) && IsBDBFile(BDBDataFile(path))) {
246 options.require_format = DatabaseFormat::BERKELEY_RO;
247 }
248
249 bilingual_str error;
250 std::unique_ptr<WalletDatabase> database = MakeDatabase(path, options, status, error);
251 if (!database) {
252 tfm::format(std::cerr, "%s\n", error.original);
253 return false;
254 }
255
256 if (database->Format().starts_with("bdb")) {
257 tfm::format(std::cerr, "dump: WARNING: BDB-backed wallets have a wallet id that is not currently dumped.\n");
258 }
259
260 bool ret = DumpWallet(*database, error, dump_filename);
261 if (!ret && !error.empty()) {
262 tfm::format(std::cerr, "%s\n", error.original);
263 return ret;
264 }
265 tfm::format(std::cerr, "The dumpfile may contain private keys. To ensure the safety of your Limenka, do not share the dumpfile.\n");
266 return ret;
267 } else if (command == "createfromdump") {
268 bilingual_str error;
269 std::vector<bilingual_str> warnings;
270 bool ret = CreateFromDump(args, name, path, error, warnings);
271 for (const auto& warning : warnings) {
272 tfm::format(std::cerr, "%s\n", warning.original);
273 }
274 if (!ret && !error.empty()) {
275 tfm::format(std::cerr, "%s\n", error.original);
276 }
277 return ret;
278 } else if (command == "importfromcoldcard") {
279 tfm::format(std::cerr, "WARNING: The \"importfromcoldcard\" command is experimental and will likely be removed or changed incompatibly in a future version.\n");
280
281 std::string filename = gArgs.GetArg("-dumpfile", "");
282 if (filename.empty()) {
283 tfm::format(std::cerr, "To use importfromcoldcard, -dumpfile=<filename> must be provided.\n");
284 return false;
285 }
286
287 const fs::path import_file_path{fs::absolute(fs::PathFromString(filename))};
288 if (!fs::exists(import_file_path)) {
289 tfm::format(std::cerr, "File %s does not exist.\n", fs::PathToString(import_file_path));
290 return false;
291 }
292
293 UniValue descriptors;
294 if (!ReadAndParseColdcardFile(import_file_path, descriptors)) {
295 return false;
296 }
297
298 DatabaseOptions options;
299 options.require_create = true;
300 options.create_flags |= WALLET_FLAG_DESCRIPTORS;
301 options.create_flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
302 options.create_flags |= WALLET_FLAG_BLANK_WALLET;
303 options.require_format = DatabaseFormat::SQLITE;
304 std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
305 if (!wallet_instance) {
306 return false;
307 }
308
309 LOCK(wallet_instance->cs_wallet);
310 for (const UniValue& descriptor : descriptors.getValues()) {
311 const UniValue result = ProcessDescriptorImport(*wallet_instance, descriptor, 0);
312 tfm::format(std::cerr, "%s\n", result.write(2));
313 }
314
315 WalletShowInfo(wallet_instance.get());
316 wallet_instance->Close();
317 } else {
318 tfm::format(std::cerr, "Invalid command: %s\n", command);
319 return false;
320 }
321
322 return true;
323 }
324 } // namespace WalletTool
325 } // namespace wallet
326