Line data Source code
1 : // Copyright (c) 2009-2014 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 "base58.h"
6 : #include "chain.h"
7 : #include "rpcserver.h"
8 : #include "init.h"
9 : #include "main.h"
10 : #include "script/script.h"
11 : #include "script/standard.h"
12 : #include "sync.h"
13 : #include "util.h"
14 : #include "utiltime.h"
15 : #include "wallet.h"
16 :
17 : #include <fstream>
18 : #include <stdint.h>
19 :
20 : #include <boost/algorithm/string.hpp>
21 : #include <boost/date_time/posix_time/posix_time.hpp>
22 :
23 : #include <univalue.h>
24 :
25 : #include <boost/foreach.hpp>
26 :
27 : using namespace std;
28 :
29 : void EnsureWalletIsUnlocked();
30 : bool EnsureWalletIsAvailable(bool avoidException);
31 :
32 : std::string static EncodeDumpTime(int64_t nTime) {
33 343 : return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime);
34 : }
35 :
36 337 : int64_t static DecodeDumpTime(const std::string &str) {
37 337 : static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0);
38 3 : static const std::locale loc(std::locale::classic(),
39 346 : new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ"));
40 337 : std::istringstream iss(str);
41 337 : iss.imbue(loc);
42 : boost::posix_time::ptime ptime(boost::date_time::not_a_date_time);
43 337 : iss >> ptime;
44 337 : if (ptime.is_not_a_date_time())
45 : return 0;
46 337 : return (ptime - epoch).total_seconds();
47 : }
48 :
49 18 : std::string static EncodeDumpString(const std::string &str) {
50 18 : std::stringstream ret;
51 90 : BOOST_FOREACH(unsigned char c, str) {
52 0 : if (c <= 32 || c >= 128 || c == '%') {
53 0 : ret << '%' << HexStr(&c, &c + 1);
54 : } else {
55 0 : ret << c;
56 : }
57 : }
58 18 : return ret.str();
59 : }
60 :
61 18 : std::string DecodeDumpString(const std::string &str) {
62 18 : std::stringstream ret;
63 18 : for (unsigned int pos = 0; pos < str.length(); pos++) {
64 0 : unsigned char c = str[pos];
65 0 : if (c == '%' && pos+2 < str.length()) {
66 0 : c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) |
67 0 : ((str[pos+2]>>6)*9+((str[pos+2]-'0')&15));
68 0 : pos += 2;
69 : }
70 0 : ret << c;
71 : }
72 18 : return ret.str();
73 : }
74 :
75 0 : UniValue importprivkey(const UniValue& params, bool fHelp)
76 : {
77 0 : if (!EnsureWalletIsAvailable(fHelp))
78 0 : return NullUniValue;
79 :
80 0 : if (fHelp || params.size() < 1 || params.size() > 3)
81 : throw runtime_error(
82 : "importprivkey \"bitcoinprivkey\" ( \"label\" rescan )\n"
83 : "\nAdds a private key (as returned by dumpprivkey) to your wallet.\n"
84 : "\nArguments:\n"
85 : "1. \"bitcoinprivkey\" (string, required) The private key (see dumpprivkey)\n"
86 : "2. \"label\" (string, optional, default=\"\") An optional label\n"
87 : "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
88 : "\nNote: This call can take minutes to complete if rescan is true.\n"
89 : "\nExamples:\n"
90 : "\nDump a private key\n"
91 0 : + HelpExampleCli("dumpprivkey", "\"myaddress\"") +
92 : "\nImport the private key with rescan\n"
93 0 : + HelpExampleCli("importprivkey", "\"mykey\"") +
94 : "\nImport using a label and without rescan\n"
95 0 : + HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") +
96 : "\nAs a JSON-RPC call\n"
97 0 : + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")
98 0 : );
99 :
100 :
101 0 : LOCK2(cs_main, pwalletMain->cs_wallet);
102 :
103 0 : EnsureWalletIsUnlocked();
104 :
105 0 : string strSecret = params[0].get_str();
106 0 : string strLabel = "";
107 0 : if (params.size() > 1)
108 0 : strLabel = params[1].get_str();
109 :
110 : // Whether to perform rescan after import
111 0 : bool fRescan = true;
112 0 : if (params.size() > 2)
113 0 : fRescan = params[2].get_bool();
114 :
115 0 : if (fRescan && fPruneMode)
116 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
117 :
118 : CBitcoinSecret vchSecret;
119 0 : bool fGood = vchSecret.SetString(strSecret);
120 :
121 0 : if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
122 :
123 0 : CKey key = vchSecret.GetKey();
124 0 : if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range");
125 :
126 0 : CPubKey pubkey = key.GetPubKey();
127 0 : assert(key.VerifyPubKey(pubkey));
128 0 : CKeyID vchAddress = pubkey.GetID();
129 : {
130 0 : pwalletMain->MarkDirty();
131 0 : pwalletMain->SetAddressBook(vchAddress, strLabel, "receive");
132 :
133 : // Don't throw error in case a key is already there
134 0 : if (pwalletMain->HaveKey(vchAddress))
135 0 : return NullUniValue;
136 :
137 0 : pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = 1;
138 :
139 0 : if (!pwalletMain->AddKeyPubKey(key, pubkey))
140 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
141 :
142 : // whenever a key is imported, we need to scan the whole chain
143 0 : pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value'
144 :
145 0 : if (fRescan) {
146 0 : pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
147 : }
148 : }
149 :
150 0 : return NullUniValue;
151 : }
152 :
153 : void ImportAddress(const CBitcoinAddress& address, const string& strLabel);
154 4 : void ImportScript(const CScript& script, const string& strLabel, bool isRedeemScript)
155 : {
156 4 : if (!isRedeemScript && ::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE)
157 0 : throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
158 :
159 4 : pwalletMain->MarkDirty();
160 :
161 4 : if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script))
162 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
163 :
164 4 : if (isRedeemScript) {
165 1 : if (!pwalletMain->HaveCScript(script) && !pwalletMain->AddCScript(script))
166 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
167 4 : ImportAddress(CBitcoinAddress(CScriptID(script)), strLabel);
168 : }
169 4 : }
170 :
171 2 : void ImportAddress(const CBitcoinAddress& address, const string& strLabel)
172 : {
173 4 : CScript script = GetScriptForDestination(address.Get());
174 2 : ImportScript(script, strLabel, false);
175 : // add to address book or update label
176 2 : if (address.IsValid())
177 8 : pwalletMain->SetAddressBook(address.Get(), strLabel, "receive");
178 2 : }
179 :
180 1 : UniValue importaddress(const UniValue& params, bool fHelp)
181 : {
182 1 : if (!EnsureWalletIsAvailable(fHelp))
183 0 : return NullUniValue;
184 :
185 3 : if (fHelp || params.size() < 1 || params.size() > 4)
186 : throw runtime_error(
187 : "importaddress \"address\" ( \"label\" rescan p2sh )\n"
188 : "\nAdds a script (in hex) or address that can be watched as if it were in your wallet but cannot be used to spend.\n"
189 : "\nArguments:\n"
190 : "1. \"script\" (string, required) The hex-encoded script (or address)\n"
191 : "2. \"label\" (string, optional, default=\"\") An optional label\n"
192 : "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
193 : "4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n"
194 : "\nNote: This call can take minutes to complete if rescan is true.\n"
195 : "If you have the full public key, you should call importpublickey instead of this.\n"
196 : "\nExamples:\n"
197 : "\nImport a script with rescan\n"
198 0 : + HelpExampleCli("importaddress", "\"myscript\"") +
199 : "\nImport using a label without rescan\n"
200 0 : + HelpExampleCli("importaddress", "\"myscript\" \"testing\" false") +
201 : "\nAs a JSON-RPC call\n"
202 0 : + HelpExampleRpc("importaddress", "\"myscript\", \"testing\", false")
203 0 : );
204 :
205 :
206 2 : string strLabel = "";
207 1 : if (params.size() > 1)
208 2 : strLabel = params[1].get_str();
209 :
210 : // Whether to perform rescan after import
211 1 : bool fRescan = true;
212 1 : if (params.size() > 2)
213 1 : fRescan = params[2].get_bool();
214 :
215 1 : if (fRescan && fPruneMode)
216 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
217 :
218 : // Whether to import a p2sh version, too
219 1 : bool fP2SH = false;
220 1 : if (params.size() > 3)
221 1 : fP2SH = params[3].get_bool();
222 :
223 1 : LOCK2(cs_main, pwalletMain->cs_wallet);
224 :
225 2 : CBitcoinAddress address(params[0].get_str());
226 1 : if (address.IsValid()) {
227 0 : if (fP2SH)
228 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
229 0 : ImportAddress(address, strLabel);
230 2 : } else if (IsHex(params[0].get_str())) {
231 2 : std::vector<unsigned char> data(ParseHex(params[0].get_str()));
232 5 : ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH);
233 : } else {
234 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
235 : }
236 :
237 1 : if (fRescan)
238 : {
239 0 : pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
240 0 : pwalletMain->ReacceptWalletTransactions();
241 : }
242 :
243 1 : return NullUniValue;
244 : }
245 :
246 1 : UniValue importpubkey(const UniValue& params, bool fHelp)
247 : {
248 1 : if (!EnsureWalletIsAvailable(fHelp))
249 0 : return NullUniValue;
250 :
251 3 : if (fHelp || params.size() < 1 || params.size() > 4)
252 : throw runtime_error(
253 : "importpubkey \"pubkey\" ( \"label\" rescan )\n"
254 : "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n"
255 : "\nArguments:\n"
256 : "1. \"pubkey\" (string, required) The hex-encoded public key\n"
257 : "2. \"label\" (string, optional, default=\"\") An optional label\n"
258 : "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
259 : "\nNote: This call can take minutes to complete if rescan is true.\n"
260 : "\nExamples:\n"
261 : "\nImport a public key with rescan\n"
262 0 : + HelpExampleCli("importpubkey", "\"mypubkey\"") +
263 : "\nImport using a label without rescan\n"
264 0 : + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") +
265 : "\nAs a JSON-RPC call\n"
266 0 : + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false")
267 0 : );
268 :
269 :
270 2 : string strLabel = "";
271 1 : if (params.size() > 1)
272 2 : strLabel = params[1].get_str();
273 :
274 : // Whether to perform rescan after import
275 1 : bool fRescan = true;
276 1 : if (params.size() > 2)
277 1 : fRescan = params[2].get_bool();
278 :
279 1 : if (fRescan && fPruneMode)
280 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
281 :
282 2 : if (!IsHex(params[0].get_str()))
283 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
284 2 : std::vector<unsigned char> data(ParseHex(params[0].get_str()));
285 : CPubKey pubKey(data.begin(), data.end());
286 1 : if (!pubKey.IsFullyValid())
287 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
288 :
289 1 : LOCK2(cs_main, pwalletMain->cs_wallet);
290 :
291 4 : ImportAddress(CBitcoinAddress(pubKey.GetID()), strLabel);
292 2 : ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false);
293 :
294 1 : if (fRescan)
295 : {
296 1 : pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
297 1 : pwalletMain->ReacceptWalletTransactions();
298 : }
299 :
300 1 : return NullUniValue;
301 : }
302 :
303 :
304 3 : UniValue importwallet(const UniValue& params, bool fHelp)
305 : {
306 3 : if (!EnsureWalletIsAvailable(fHelp))
307 0 : return NullUniValue;
308 :
309 6 : if (fHelp || params.size() != 1)
310 : throw runtime_error(
311 : "importwallet \"filename\"\n"
312 : "\nImports keys from a wallet dump file (see dumpwallet).\n"
313 : "\nArguments:\n"
314 : "1. \"filename\" (string, required) The wallet file\n"
315 : "\nExamples:\n"
316 : "\nDump the wallet\n"
317 0 : + HelpExampleCli("dumpwallet", "\"test\"") +
318 : "\nImport the wallet\n"
319 0 : + HelpExampleCli("importwallet", "\"test\"") +
320 : "\nImport using the json rpc call\n"
321 0 : + HelpExampleRpc("importwallet", "\"test\"")
322 0 : );
323 :
324 3 : if (fPruneMode)
325 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode");
326 :
327 3 : LOCK2(cs_main, pwalletMain->cs_wallet);
328 :
329 3 : EnsureWalletIsUnlocked();
330 :
331 6 : ifstream file;
332 9 : file.open(params[0].get_str().c_str(), std::ios::in | std::ios::ate);
333 3 : if (!file.is_open())
334 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
335 :
336 6 : int64_t nTimeBegin = chainActive.Tip()->GetBlockTime();
337 :
338 3 : bool fGood = true;
339 :
340 6 : int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
341 3 : file.seekg(0, file.beg);
342 :
343 6 : pwalletMain->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI
344 367 : while (file.good()) {
345 1805 : pwalletMain->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))));
346 : std::string line;
347 361 : std::getline(file, line);
348 713 : if (line.empty() || line[0] == '#')
349 : continue;
350 :
351 337 : std::vector<std::string> vstr;
352 337 : boost::split(vstr, line, boost::is_any_of(" "));
353 674 : if (vstr.size() < 2)
354 0 : continue;
355 : CBitcoinSecret vchSecret;
356 337 : if (!vchSecret.SetString(vstr[0]))
357 : continue;
358 337 : CKey key = vchSecret.GetKey();
359 337 : CPubKey pubkey = key.GetPubKey();
360 337 : assert(key.VerifyPubKey(pubkey));
361 337 : CKeyID keyid = pubkey.GetID();
362 337 : if (pwalletMain->HaveKey(keyid)) {
363 0 : LogPrintf("Skipping import of %s (key already present)\n", CBitcoinAddress(keyid).ToString());
364 0 : continue;
365 : }
366 674 : int64_t nTime = DecodeDumpTime(vstr[1]);
367 : std::string strLabel;
368 337 : bool fLabel = true;
369 1348 : for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
370 2022 : if (boost::algorithm::starts_with(vstr[nStr], "#"))
371 : break;
372 674 : if (vstr[nStr] == "change=1")
373 19 : fLabel = false;
374 1011 : if (vstr[nStr] == "reserve=1")
375 300 : fLabel = false;
376 1011 : if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
377 54 : strLabel = DecodeDumpString(vstr[nStr].substr(6));
378 18 : fLabel = true;
379 : }
380 : }
381 1348 : LogPrintf("Importing %s...\n", CBitcoinAddress(keyid).ToString());
382 337 : if (!pwalletMain->AddKeyPubKey(key, pubkey)) {
383 0 : fGood = false;
384 : continue;
385 : }
386 337 : pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime;
387 337 : if (fLabel)
388 90 : pwalletMain->SetAddressBook(keyid, strLabel, "receive");
389 337 : nTimeBegin = std::min(nTimeBegin, nTime);
390 : }
391 3 : file.close();
392 9 : pwalletMain->ShowProgress("", 100); // hide progress dialog in GUI
393 :
394 3 : CBlockIndex *pindex = chainActive.Tip();
395 1290 : while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - 7200)
396 642 : pindex = pindex->pprev;
397 :
398 3 : if (!pwalletMain->nTimeFirstKey || nTimeBegin < pwalletMain->nTimeFirstKey)
399 3 : pwalletMain->nTimeFirstKey = nTimeBegin;
400 :
401 3 : LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->nHeight + 1);
402 3 : pwalletMain->ScanForWalletTransactions(pindex);
403 3 : pwalletMain->MarkDirty();
404 :
405 3 : if (!fGood)
406 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet");
407 :
408 3 : return NullUniValue;
409 : }
410 :
411 0 : UniValue dumpprivkey(const UniValue& params, bool fHelp)
412 : {
413 0 : if (!EnsureWalletIsAvailable(fHelp))
414 0 : return NullUniValue;
415 :
416 0 : if (fHelp || params.size() != 1)
417 : throw runtime_error(
418 : "dumpprivkey \"bitcoinaddress\"\n"
419 : "\nReveals the private key corresponding to 'bitcoinaddress'.\n"
420 : "Then the importprivkey can be used with this output\n"
421 : "\nArguments:\n"
422 : "1. \"bitcoinaddress\" (string, required) The bitcoin address for the private key\n"
423 : "\nResult:\n"
424 : "\"key\" (string) The private key\n"
425 : "\nExamples:\n"
426 0 : + HelpExampleCli("dumpprivkey", "\"myaddress\"")
427 0 : + HelpExampleCli("importprivkey", "\"mykey\"")
428 0 : + HelpExampleRpc("dumpprivkey", "\"myaddress\"")
429 0 : );
430 :
431 0 : LOCK2(cs_main, pwalletMain->cs_wallet);
432 :
433 0 : EnsureWalletIsUnlocked();
434 :
435 0 : string strAddress = params[0].get_str();
436 : CBitcoinAddress address;
437 0 : if (!address.SetString(strAddress))
438 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
439 : CKeyID keyID;
440 0 : if (!address.GetKeyID(keyID))
441 0 : throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key");
442 : CKey vchSecret;
443 0 : if (!pwalletMain->GetKey(keyID, vchSecret))
444 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known");
445 0 : return CBitcoinSecret(vchSecret).ToString();
446 : }
447 :
448 :
449 3 : UniValue dumpwallet(const UniValue& params, bool fHelp)
450 : {
451 3 : if (!EnsureWalletIsAvailable(fHelp))
452 0 : return NullUniValue;
453 :
454 6 : if (fHelp || params.size() != 1)
455 : throw runtime_error(
456 : "dumpwallet \"filename\"\n"
457 : "\nDumps all wallet keys in a human-readable format.\n"
458 : "\nArguments:\n"
459 : "1. \"filename\" (string, required) The filename\n"
460 : "\nExamples:\n"
461 0 : + HelpExampleCli("dumpwallet", "\"test\"")
462 0 : + HelpExampleRpc("dumpwallet", "\"test\"")
463 0 : );
464 :
465 3 : LOCK2(cs_main, pwalletMain->cs_wallet);
466 :
467 3 : EnsureWalletIsUnlocked();
468 :
469 6 : ofstream file;
470 9 : file.open(params[0].get_str().c_str());
471 3 : if (!file.is_open())
472 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
473 :
474 : std::map<CKeyID, int64_t> mapKeyBirth;
475 : std::set<CKeyID> setKeyPool;
476 3 : pwalletMain->GetKeyBirthTimes(mapKeyBirth);
477 3 : pwalletMain->GetAllReserveKeys(setKeyPool);
478 :
479 : // sort time/key pairs
480 : std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
481 683 : for (std::map<CKeyID, int64_t>::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) {
482 1011 : vKeyBirth.push_back(std::make_pair(it->second, it->first));
483 : }
484 : mapKeyBirth.clear();
485 : std::sort(vKeyBirth.begin(), vKeyBirth.end());
486 :
487 : // produce output
488 6 : file << strprintf("# Wallet dump created by Bitcoin %s (%s)\n", CLIENT_BUILD, CLIENT_DATE);
489 12 : file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime()));
490 15 : file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString());
491 15 : file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
492 3 : file << "\n";
493 1023 : for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
494 337 : const CKeyID &keyid = it->second;
495 337 : std::string strTime = EncodeDumpTime(it->first);
496 1011 : std::string strAddr = CBitcoinAddress(keyid).ToString();
497 : CKey key;
498 337 : if (pwalletMain->GetKey(keyid, key)) {
499 1011 : if (pwalletMain->mapAddressBook.count(keyid)) {
500 108 : file << strprintf("%s %s label=%s # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, EncodeDumpString(pwalletMain->mapAddressBook[keyid].name), strAddr);
501 319 : } else if (setKeyPool.count(keyid)) {
502 1200 : file << strprintf("%s %s reserve=1 # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, strAddr);
503 : } else {
504 76 : file << strprintf("%s %s change=1 # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, strAddr);
505 : }
506 : }
507 : }
508 3 : file << "\n";
509 3 : file << "# End of dump\n";
510 3 : file.close();
511 3 : return NullUniValue;
512 288 : }
|