Add wallet dump functionality and key creation timestamps
[novacoin.git] / src / rpcdump.cpp
1 // Copyright (c) 2009-2012 Bitcoin Developers
2 // Distributed under the MIT/X11 software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include <iostream>
6 #include <fstream>
7
8 #include "init.h" // for pwalletMain
9 #include "bitcoinrpc.h"
10 #include "ui_interface.h"
11 #include "base58.h"
12
13 #include <boost/lexical_cast.hpp>
14 #include <boost/date_time/posix_time/posix_time.hpp>
15 #include <boost/variant/get.hpp>
16 #include <boost/algorithm/string.hpp>
17
18 #define printf OutputDebugStringF
19
20 using namespace json_spirit;
21 using namespace std;
22
23 void EnsureWalletIsUnlocked();
24
25 std::string static EncodeDumpTime(int64 nTime) {
26     return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime);
27 }
28
29 int64 static DecodeDumpTime(const std::string &str) {
30     static const boost::posix_time::time_input_facet facet("%Y-%m-%dT%H:%M:%SZ");
31     static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0);
32     const std::locale loc(std::locale::classic(), &facet);
33     std::istringstream iss(str);
34     iss.imbue(loc);
35     boost::posix_time::ptime ptime(boost::date_time::not_a_date_time);
36     iss >> ptime;
37     if (ptime.is_not_a_date_time())
38         return 0;
39     return (ptime - epoch).total_seconds();
40 }
41
42 std::string static EncodeDumpString(const std::string &str) {
43     std::stringstream ret;
44     BOOST_FOREACH(unsigned char c, str) {
45         if (c <= 32 || c >= 128 || c == '%') {
46             ret << '%' << HexStr(&c, &c + 1);
47         } else {
48             ret << c;
49         }
50     }
51     return ret.str();
52 }
53
54 std::string DecodeDumpString(const std::string &str) {
55     std::stringstream ret;
56     for (unsigned int pos = 0; pos < str.length(); pos++) {
57         unsigned char c = str[pos];
58         if (c == '%' && pos+2 < str.length()) {
59             c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) | 
60                 ((str[pos+2]>>6)*9+((str[pos+2]-'0')&15));
61             pos += 2;
62         }
63         ret << c;
64     }
65     return ret.str();
66 }
67
68 class CTxDump
69 {
70 public:
71     CBlockIndex *pindex;
72     int64 nValue;
73     bool fSpent;
74     CWalletTx* ptx;
75     int nOut;
76     CTxDump(CWalletTx* ptx = NULL, int nOut = -1)
77     {
78         pindex = NULL;
79         nValue = 0;
80         fSpent = false;
81         this->ptx = ptx;
82         this->nOut = nOut;
83     }
84 };
85
86 Value importprivkey(const Array& params, bool fHelp)
87 {
88     if (fHelp || params.size() < 1 || params.size() > 2)
89         throw runtime_error(
90             "importprivkey <novacoinprivkey> [label]\n"
91             "Adds a private key (as returned by dumpprivkey) to your wallet.");
92
93     string strSecret = params[0].get_str();
94     string strLabel = "";
95     if (params.size() > 1)
96         strLabel = params[1].get_str();
97     CBitcoinSecret vchSecret;
98     bool fGood = vchSecret.SetString(strSecret);
99
100     if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
101     if (fWalletUnlockMintOnly) // ppcoin: no importprivkey in mint-only mode
102         throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Wallet is unlocked for minting only.");
103
104     CKey key;
105     bool fCompressed;
106     CSecret secret = vchSecret.GetSecret(fCompressed);
107     key.SetSecret(secret, fCompressed);
108     CKeyID vchAddress = key.GetPubKey().GetID();
109     {
110         LOCK2(cs_main, pwalletMain->cs_wallet);
111
112         pwalletMain->MarkDirty();
113         pwalletMain->SetAddressBookName(vchAddress, strLabel);
114
115         if (!pwalletMain->AddKey(key))
116             throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
117
118         pwalletMain->ScanForWalletTransactions(pindexGenesisBlock, true);
119         pwalletMain->ReacceptWalletTransactions();
120     }
121
122     return Value::null;
123 }
124
125 Value importwallet(const Array& params, bool fHelp)
126 {
127     if (fHelp || params.size() != 1)
128         throw runtime_error(
129             "importwallet <filename>\n"
130             "Imports keys from a wallet dump file (see dumpwallet).");
131
132     EnsureWalletIsUnlocked();
133
134     ifstream file;
135     file.open(params[0].get_str().c_str());
136     if (!file.is_open())
137         throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
138
139     int64 nTimeBegin = pindexBest->nTime;
140
141     bool fGood = true;
142
143     while (file.good()) {
144         std::string line;
145         std::getline(file, line);
146         if (line.empty() || line[0] == '#')
147             continue;
148
149         std::vector<std::string> vstr;
150         boost::split(vstr, line, boost::is_any_of(" "));
151         if (vstr.size() < 2)
152             continue;
153         CBitcoinSecret vchSecret;
154         if (!vchSecret.SetString(vstr[0]))
155             continue;
156         bool isCompressed;
157         CKey key;
158         CSecret secret = vchSecret.GetSecret(isCompressed);
159         key.SetSecret(secret, isCompressed);
160         CPubKey pubkey = key.GetPubKey();
161         CKeyID keyid = pubkey.GetID();
162         if (pwalletMain->HaveKey(keyid)) {
163             printf("Skipping import of %s (key already present)\n", CBitcoinAddress(keyid).ToString().c_str());
164             continue;
165         }
166         int64 nTime = DecodeDumpTime(vstr[1]);
167         std::string strLabel;
168         bool fLabel = true;
169         for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
170             if (boost::algorithm::starts_with(vstr[nStr], "#"))
171                 break;
172             if (vstr[nStr] == "change=1")
173                 fLabel = false;
174             if (vstr[nStr] == "reserve=1")
175                 fLabel = false;
176             if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
177                 strLabel = DecodeDumpString(vstr[nStr].substr(6));
178                 fLabel = true;
179             }
180         }
181         printf("Importing %s...\n", CBitcoinAddress(keyid).ToString().c_str());
182         if (!pwalletMain->AddKey(key)) {
183             fGood = false;
184             continue;
185         }
186         pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime;
187         if (fLabel)
188             pwalletMain->SetAddressBookName(keyid, strLabel);
189         nTimeBegin = std::min(nTimeBegin, nTime);
190     }
191     file.close();
192
193     CBlockIndex *pindex = pindexBest;
194     while (pindex && pindex->pprev && pindex->nTime > nTimeBegin - 7200)
195         pindex = pindex->pprev;
196
197     printf("Rescanning last %i blocks\n", pindexBest->nHeight - pindex->nHeight + 1);
198     pwalletMain->ScanForWalletTransactions(pindex);
199     pwalletMain->ReacceptWalletTransactions();
200     pwalletMain->MarkDirty();
201
202     if (!fGood)
203         throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet");
204
205     return Value::null;
206 }
207
208
209 Value dumpprivkey(const Array& params, bool fHelp)
210 {
211     if (fHelp || params.size() != 1)
212         throw runtime_error(
213             "dumpprivkey <novacoinaddress>\n"
214             "Reveals the private key corresponding to <novacoinaddress>.");
215
216     string strAddress = params[0].get_str();
217     CBitcoinAddress address;
218     if (!address.SetString(strAddress))
219         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid NovaCoin address");
220     if (fWalletUnlockMintOnly) // ppcoin: no dumpprivkey in mint-only mode
221         throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Wallet is unlocked for minting only.");
222     CKeyID keyID;
223     if (!address.GetKeyID(keyID))
224         throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key");
225     CSecret vchSecret;
226     bool fCompressed;
227     if (!pwalletMain->GetSecret(keyID, vchSecret, fCompressed))
228         throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known");
229     return CBitcoinSecret(vchSecret, fCompressed).ToString();
230 }
231
232 Value dumpwallet(const Array& params, bool fHelp)
233 {
234     if (fHelp || params.size() != 1)
235         throw runtime_error(
236             "dumpwallet <filename>\n"
237             "Dumps all wallet keys in a human-readable format.");
238
239     EnsureWalletIsUnlocked();
240
241     ofstream file;
242     file.open(params[0].get_str().c_str());
243     if (!file.is_open())
244         throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
245
246     std::map<CKeyID, int64> mapKeyBirth;
247
248     std::set<CKeyID> setKeyPool;
249
250     pwalletMain->GetKeyBirthTimes(mapKeyBirth);
251
252     pwalletMain->GetAllReserveKeys(setKeyPool);
253
254     // sort time/key pairs
255     std::vector<std::pair<int64, CKeyID> > vKeyBirth;
256     for (std::map<CKeyID, int64>::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) {
257         vKeyBirth.push_back(std::make_pair(it->second, it->first));
258     }
259     mapKeyBirth.clear();
260     std::sort(vKeyBirth.begin(), vKeyBirth.end());
261
262     // produce output
263     file << strprintf("# Wallet dump created by NovaCoin %s (%s)\n", CLIENT_BUILD.c_str(), CLIENT_DATE.c_str());
264     file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime()).c_str());
265     file << strprintf("# * Best block at time of backup was %i (%s),\n", nBestHeight, hashBestChain.ToString().c_str());
266     file << strprintf("#   mined on %s\n", EncodeDumpTime(pindexBest->nTime).c_str());
267     file << "\n";
268     for (std::vector<std::pair<int64, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
269         const CKeyID &keyid = it->second;
270         std::string strTime = EncodeDumpTime(it->first);
271         std::string strAddr = CBitcoinAddress(keyid).ToString();
272         bool IsCompressed;
273
274         CKey key;
275         if (pwalletMain->GetKey(keyid, key)) {
276             if (pwalletMain->mapAddressBook.count(keyid)) {
277                 CSecret secret = key.GetSecret(IsCompressed);
278                 file << strprintf("%s %s label=%s # addr=%s\n", CBitcoinSecret(secret, IsCompressed).ToString().c_str(), strTime.c_str(), EncodeDumpString(pwalletMain->mapAddressBook[keyid]).c_str(), strAddr.c_str());
279             } else if (setKeyPool.count(keyid)) {
280                 CSecret secret = key.GetSecret(IsCompressed);
281                 file << strprintf("%s %s reserve=1 # addr=%s\n", CBitcoinSecret(secret, IsCompressed).ToString().c_str(), strTime.c_str(), strAddr.c_str());
282             } else {
283                 CSecret secret = key.GetSecret(IsCompressed);
284                 file << strprintf("%s %s change=1 # addr=%s\n", CBitcoinSecret(secret, IsCompressed).ToString().c_str(), strTime.c_str(), strAddr.c_str());
285             }
286         }
287     }
288     file << "\n";
289     file << "# End of dump\n";
290     file.close();
291     return Value::null;
292 }