From ac92f740d9fe40de422e8446aa514a254f250f27 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 28 Jul 2013 22:45:12 +0400 Subject: [PATCH] Add wallet dump functionality and key creation timestamps --- src/bitcoinrpc.cpp | 2 + src/bitcoinrpc.h | 4 +- src/rpcdump.cpp | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/script.cpp | 37 ++++++++++ src/script.h | 1 + src/wallet.cpp | 82 ++++++++++++++++++++-- src/wallet.h | 13 +++- src/walletdb.cpp | 98 ++++++++++++++++++-------- src/walletdb.h | 47 ++++++++++++- 9 files changed, 441 insertions(+), 40 deletions(-) diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 50211f6..e63ec8f 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -251,6 +251,8 @@ static const CRPCCommand vRPCCommands[] = { "submitblock", &submitblock, false, false }, { "listsinceblock", &listsinceblock, false, false }, { "dumpprivkey", &dumpprivkey, false, false }, + { "dumpwallet", &dumpwallet, true, false }, + { "importwallet", &importwallet, false, false }, { "importprivkey", &importprivkey, false, false }, { "listunspent", &listunspent, false, false }, { "getrawtransaction", &getrawtransaction, false, false }, diff --git a/src/bitcoinrpc.h b/src/bitcoinrpc.h index a800dca..f19bcde 100644 --- a/src/bitcoinrpc.h +++ b/src/bitcoinrpc.h @@ -131,11 +131,13 @@ extern std::string HexBits(unsigned int nBits); extern std::string HelpRequiringPassphrase(); extern void EnsureWalletIsUnlocked(); - extern json_spirit::Value getconnectioncount(const json_spirit::Array& params, bool fHelp); // in rpcnet.cpp extern json_spirit::Value getpeerinfo(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value dumpwallet(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value importwallet(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value dumpprivkey(const json_spirit::Array& params, bool fHelp); // in rpcdump.cpp extern json_spirit::Value importprivkey(const json_spirit::Array& params, bool fHelp); + extern json_spirit::Value sendalert(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getmininginfo(const json_spirit::Array& params, bool fHelp); diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index e287742..a5a2dad 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -2,18 +2,69 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include +#include + #include "init.h" // for pwalletMain #include "bitcoinrpc.h" #include "ui_interface.h" #include "base58.h" #include +#include +#include +#include #define printf OutputDebugStringF using namespace json_spirit; using namespace std; +void EnsureWalletIsUnlocked(); + +std::string static EncodeDumpTime(int64 nTime) { + return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime); +} + +int64 static DecodeDumpTime(const std::string &str) { + static const boost::posix_time::time_input_facet facet("%Y-%m-%dT%H:%M:%SZ"); + static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); + const std::locale loc(std::locale::classic(), &facet); + std::istringstream iss(str); + iss.imbue(loc); + boost::posix_time::ptime ptime(boost::date_time::not_a_date_time); + iss >> ptime; + if (ptime.is_not_a_date_time()) + return 0; + return (ptime - epoch).total_seconds(); +} + +std::string static EncodeDumpString(const std::string &str) { + std::stringstream ret; + BOOST_FOREACH(unsigned char c, str) { + if (c <= 32 || c >= 128 || c == '%') { + ret << '%' << HexStr(&c, &c + 1); + } else { + ret << c; + } + } + return ret.str(); +} + +std::string DecodeDumpString(const std::string &str) { + std::stringstream ret; + for (unsigned int pos = 0; pos < str.length(); pos++) { + unsigned char c = str[pos]; + if (c == '%' && pos+2 < str.length()) { + c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) | + ((str[pos+2]>>6)*9+((str[pos+2]-'0')&15)); + pos += 2; + } + ret << c; + } + return ret.str(); +} + class CTxDump { public: @@ -71,6 +122,90 @@ Value importprivkey(const Array& params, bool fHelp) return Value::null; } +Value importwallet(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "importwallet \n" + "Imports keys from a wallet dump file (see dumpwallet)."); + + EnsureWalletIsUnlocked(); + + ifstream file; + file.open(params[0].get_str().c_str()); + if (!file.is_open()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); + + int64 nTimeBegin = pindexBest->nTime; + + bool fGood = true; + + while (file.good()) { + std::string line; + std::getline(file, line); + if (line.empty() || line[0] == '#') + continue; + + std::vector vstr; + boost::split(vstr, line, boost::is_any_of(" ")); + if (vstr.size() < 2) + continue; + CBitcoinSecret vchSecret; + if (!vchSecret.SetString(vstr[0])) + continue; + bool isCompressed; + CKey key; + CSecret secret = vchSecret.GetSecret(isCompressed); + key.SetSecret(secret, isCompressed); + CPubKey pubkey = key.GetPubKey(); + CKeyID keyid = pubkey.GetID(); + if (pwalletMain->HaveKey(keyid)) { + printf("Skipping import of %s (key already present)\n", CBitcoinAddress(keyid).ToString().c_str()); + continue; + } + int64 nTime = DecodeDumpTime(vstr[1]); + std::string strLabel; + bool fLabel = true; + for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { + if (boost::algorithm::starts_with(vstr[nStr], "#")) + break; + if (vstr[nStr] == "change=1") + fLabel = false; + if (vstr[nStr] == "reserve=1") + fLabel = false; + if (boost::algorithm::starts_with(vstr[nStr], "label=")) { + strLabel = DecodeDumpString(vstr[nStr].substr(6)); + fLabel = true; + } + } + printf("Importing %s...\n", CBitcoinAddress(keyid).ToString().c_str()); + if (!pwalletMain->AddKey(key)) { + fGood = false; + continue; + } + pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime; + if (fLabel) + pwalletMain->SetAddressBookName(keyid, strLabel); + nTimeBegin = std::min(nTimeBegin, nTime); + } + file.close(); + + CBlockIndex *pindex = pindexBest; + while (pindex && pindex->pprev && pindex->nTime > nTimeBegin - 7200) + pindex = pindex->pprev; + + printf("Rescanning last %i blocks\n", pindexBest->nHeight - pindex->nHeight + 1); + pwalletMain->ScanForWalletTransactions(pindex); + pwalletMain->ReacceptWalletTransactions(); + pwalletMain->MarkDirty(); + + if (!fGood) + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet"); + + return Value::null; +} + + Value dumpprivkey(const Array& params, bool fHelp) { if (fHelp || params.size() != 1) @@ -93,3 +228,65 @@ Value dumpprivkey(const Array& params, bool fHelp) throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); return CBitcoinSecret(vchSecret, fCompressed).ToString(); } + +Value dumpwallet(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "dumpwallet \n" + "Dumps all wallet keys in a human-readable format."); + + EnsureWalletIsUnlocked(); + + ofstream file; + file.open(params[0].get_str().c_str()); + if (!file.is_open()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); + + std::map mapKeyBirth; + + std::set setKeyPool; + + pwalletMain->GetKeyBirthTimes(mapKeyBirth); + + pwalletMain->GetAllReserveKeys(setKeyPool); + + // sort time/key pairs + std::vector > vKeyBirth; + for (std::map::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) { + vKeyBirth.push_back(std::make_pair(it->second, it->first)); + } + mapKeyBirth.clear(); + std::sort(vKeyBirth.begin(), vKeyBirth.end()); + + // produce output + file << strprintf("# Wallet dump created by NovaCoin %s (%s)\n", CLIENT_BUILD.c_str(), CLIENT_DATE.c_str()); + file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime()).c_str()); + file << strprintf("# * Best block at time of backup was %i (%s),\n", nBestHeight, hashBestChain.ToString().c_str()); + file << strprintf("# mined on %s\n", EncodeDumpTime(pindexBest->nTime).c_str()); + file << "\n"; + for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { + const CKeyID &keyid = it->second; + std::string strTime = EncodeDumpTime(it->first); + std::string strAddr = CBitcoinAddress(keyid).ToString(); + bool IsCompressed; + + CKey key; + if (pwalletMain->GetKey(keyid, key)) { + if (pwalletMain->mapAddressBook.count(keyid)) { + CSecret secret = key.GetSecret(IsCompressed); + 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()); + } else if (setKeyPool.count(keyid)) { + CSecret secret = key.GetSecret(IsCompressed); + file << strprintf("%s %s reserve=1 # addr=%s\n", CBitcoinSecret(secret, IsCompressed).ToString().c_str(), strTime.c_str(), strAddr.c_str()); + } else { + CSecret secret = key.GetSecret(IsCompressed); + file << strprintf("%s %s change=1 # addr=%s\n", CBitcoinSecret(secret, IsCompressed).ToString().c_str(), strTime.c_str(), strAddr.c_str()); + } + } + } + file << "\n"; + file << "# End of dump\n"; + file.close(); + return Value::null; +} diff --git a/src/script.cpp b/src/script.cpp index 5ef4d5d..645b67c 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1539,6 +1539,43 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) return false; } +class CAffectedKeysVisitor : public boost::static_visitor { +private: + const CKeyStore &keystore; + std::vector &vKeys; + +public: + CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {} + + void Process(const CScript &script) { + txnouttype type; + std::vector vDest; + int nRequired; + if (ExtractDestinations(script, type, vDest, nRequired)) { + BOOST_FOREACH(const CTxDestination &dest, vDest) + boost::apply_visitor(*this, dest); + } + } + + void operator()(const CKeyID &keyId) { + if (keystore.HaveKey(keyId)) + vKeys.push_back(keyId); + } + + void operator()(const CScriptID &scriptId) { + CScript script; + if (keystore.GetCScript(scriptId, script)) + Process(script); + } + + void operator()(const CNoDestination &none) {} +}; + + +void ExtractAffectedKeys(const CKeyStore &keystore, const CScript& scriptPubKey, std::vector &vKeys) { + CAffectedKeysVisitor(keystore, vKeys).Process(scriptPubKey); +} + bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, vector& addressRet, int& nRequiredRet) { addressRet.clear(); diff --git a/src/script.h b/src/script.h index 99465cb..711b03c 100644 --- a/src/script.h +++ b/src/script.h @@ -591,6 +591,7 @@ int ScriptSigArgsExpected(txnouttype t, const std::vector &vKeys); bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet); bool ExtractDestinations(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& addressRet, int& nRequiredRet); bool SignSignature(const CKeyStore& keystore, const CScript& fromPubKey, CTransaction& txTo, unsigned int nIn, int nHashType=SIGHASH_ALL); diff --git a/src/wallet.cpp b/src/wallet.cpp index 2b5cfa2..9a37be5 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -45,19 +45,29 @@ CPubKey CWallet::GenerateNewKey() return key.GetPubKey(); } -bool CWallet::AddKey(const CKey& key) +bool CWallet::AddKey(const CKey& key, int64 nCreateTime) { + if(!nCreateTime) + nCreateTime = GetTime(); + if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) + nTimeFirstKey = nCreateTime; + if (!CCryptoKeyStore::AddKey(key)) return false; if (!fFileBacked) return true; if (!IsCrypted()) - return CWalletDB(strWalletFile).WriteKey(key.GetPubKey(), key.GetPrivKey()); + return CWalletDB(strWalletFile).WriteKey(key.GetPubKey(), key.GetPrivKey(), nCreateTime); return true; } -bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const vector &vchCryptedSecret) +bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const vector &vchCryptedSecret, int64 nCreateTime) { + if(!nCreateTime) + nCreateTime = GetTime(); + if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) + nTimeFirstKey = nCreateTime; + if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) return false; if (!fFileBacked) @@ -65,13 +75,22 @@ bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const vectorWriteCryptedKey(vchPubKey, vchCryptedSecret); + return pwalletdbEncryption->WriteCryptedKey(vchPubKey, vchCryptedSecret, nCreateTime); else - return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, vchCryptedSecret); + return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, vchCryptedSecret, nCreateTime); } return false; } +bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta) +{ + if (meta.nCreateTime && (!nTimeFirstKey || meta.nCreateTime < nTimeFirstKey)) + nTimeFirstKey = meta.nCreateTime; + + mapKeyMetadata[pubkey.GetID()] = meta; + return true; +} + bool CWallet::AddCScript(const CScript& redeemScript) { if (!CCryptoKeyStore::AddCScript(redeemScript)) @@ -2194,7 +2213,7 @@ void CReserveKey::ReturnKey() vchPubKey = CPubKey(); } -void CWallet::GetAllReserveKeys(set& setAddress) +void CWallet::GetAllReserveKeys(set& setAddress) const { setAddress.clear(); @@ -2224,3 +2243,54 @@ void CWallet::UpdatedTransaction(const uint256 &hashTx) NotifyTransactionChanged(this, hashTx, CT_UPDATED); } } + +void CWallet::GetKeyBirthTimes(std::map &mapKeyBirth) const { + mapKeyBirth.clear(); + + // get birth times for keys with metadata + for (std::map::const_iterator it = mapKeyMetadata.begin(); it != mapKeyMetadata.end(); it++) + if (it->second.nCreateTime) + mapKeyBirth[it->first] = it->second.nCreateTime; + + // map in which we'll infer heights of other keys + CBlockIndex *pindexMax = FindBlockByHeight(std::max(0, nBestHeight - 144)); // the tip can be reorganised; use a 144-block safety margin + std::map mapKeyFirstBlock; + std::set setKeys; + GetKeys(setKeys); + BOOST_FOREACH(const CKeyID &keyid, setKeys) { + if (mapKeyBirth.count(keyid) == 0) + mapKeyFirstBlock[keyid] = pindexMax; + } + setKeys.clear(); + + // if there are no such keys, we're done + if (mapKeyFirstBlock.empty()) + return; + + // find first block that affects those keys, if there are any left + std::vector vAffected; + for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); it++) { + // iterate over all wallet transactions... + const CWalletTx &wtx = (*it).second; + std::map::const_iterator blit = mapBlockIndex.find(wtx.hashBlock); + if (blit != mapBlockIndex.end() && blit->second->IsInMainChain()) { + // ... which are already in a block + int nHeight = blit->second->nHeight; + BOOST_FOREACH(const CTxOut &txout, wtx.vout) { + // iterate over all their outputs + ::ExtractAffectedKeys(*this, txout.scriptPubKey, vAffected); + BOOST_FOREACH(const CKeyID &keyid, vAffected) { + // ... and all their affected keys + std::map::iterator rit = mapKeyFirstBlock.find(keyid); + if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight) + rit->second = blit->second; + } + vAffected.clear(); + } + } + } + + // Extract block timestamps for those keys + for (std::map::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++) + mapKeyBirth[it->first] = it->second->nTime - 7200; // block times can be 2h off +} diff --git a/src/wallet.h b/src/wallet.h index 17c54eb..3060238 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -94,6 +94,7 @@ public: std::string strWalletFile; std::set setKeyPool; + std::map mapKeyMetadata; typedef std::map MasterKeyMap; @@ -127,6 +128,7 @@ public: std::map mapAddressBook; CPubKey vchDefaultKey; + int64 nTimeFirstKey; // check whether we are allowed to upgrade (or already support) to the named feature bool CanSupportFeature(enum WalletFeature wf) { return nWalletMaxVersion >= wf; } @@ -137,14 +139,16 @@ public: // Generate a new key CPubKey GenerateNewKey(); // Adds a key to the store, and saves it to disk. - bool AddKey(const CKey& key); + bool AddKey(const CKey& key, int64 nCreateTime = 0); // Adds a key to the store, without saving it to disk (used by LoadWallet) bool LoadKey(const CKey& key) { return CCryptoKeyStore::AddKey(key); } + // Load metadata (used by LoadWallet) + bool LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &metadata); bool LoadMinVersion(int nVersion) { nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } // Adds an encrypted key to the store, and saves it to disk. - bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); + bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret, int64 nCreateTime = 0); // Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) { SetMinVersion(FEATURE_WALLETCRYPT); return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); } bool AddCScript(const CScript& redeemScript); @@ -154,6 +158,9 @@ public: bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); + void GetKeyBirthTimes(std::map &mapKeyBirth) const; + + /** Increment the next transaction order id @return next transaction order id */ @@ -200,7 +207,7 @@ public: void ReturnKey(int64 nIndex); bool GetKeyFromPool(CPubKey &key, bool fAllowReuse=true); int64 GetOldestKeyPoolTime(); - void GetAllReserveKeys(std::set& setAddress); + void GetAllReserveKeys(std::set& setAddress) const; std::set< std::set > GetAddressGroupings(); std::map GetAddressBalances(); diff --git a/src/walletdb.cpp b/src/walletdb.cpp index ad24172..e538fc5 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -181,11 +181,27 @@ CWalletDB::ReorderTransactions(CWallet* pwallet) return DB_LOAD_OK; } +class CWalletScanState { +public: + unsigned int nKeys; + unsigned int nCKeys; + unsigned int nKeyMeta; + bool fIsEncrypted; + bool fAnyUnordered; + int nFileVersion; + vector vWalletUpgrade; + + CWalletScanState() { + nKeys = nCKeys = nKeyMeta = 0; + fIsEncrypted = false; + fAnyUnordered = false; + nFileVersion = 0; + } +}; bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, - int& nFileVersion, vector& vWalletUpgrade, - bool& fIsEncrypted, bool& fAnyUnordered, string& strType, string& strErr) + CWalletScanState &wss, string& strType, string& strErr) { try { // Unserialize @@ -229,11 +245,11 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strErr = strprintf("LoadWallet() repairing tx ver=%d %s", wtx.fTimeReceivedIsTxTime, hash.ToString().c_str()); wtx.fTimeReceivedIsTxTime = 0; } - vWalletUpgrade.push_back(hash); + wss.vWalletUpgrade.push_back(hash); } if (wtx.nOrderPos == -1) - fAnyUnordered = true; + wss.fAnyUnordered = true; //// debug print //printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str()); @@ -252,12 +268,12 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, if (nNumber > nAccountingEntryNumber) nAccountingEntryNumber = nNumber; - if (!fAnyUnordered) + if (!wss.fAnyUnordered) { CAccountingEntry acentry; ssValue >> acentry; if (acentry.nOrderPos == -1) - fAnyUnordered = true; + wss.fAnyUnordered = true; } } else if (strType == "key" || strType == "wkey") @@ -267,6 +283,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CKey key; if (strType == "key") { + wss.nKeys++; CPrivKey pkey; ssValue >> pkey; key.SetPubKey(vchPubKey); @@ -330,6 +347,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, } else if (strType == "ckey") { + wss.nCKeys++; vector vchPubKey; ssKey >> vchPubKey; vector vchPrivKey; @@ -339,7 +357,22 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strErr = "Error reading wallet database: LoadCryptedKey failed"; return false; } - fIsEncrypted = true; + wss.fIsEncrypted = true; + } + else if (strType == "keymeta") + { + CPubKey vchPubKey; + ssKey >> vchPubKey; + CKeyMetadata keyMeta; + ssValue >> keyMeta; + wss.nKeyMeta++; + + pwallet->LoadKeyMetadata(vchPubKey, keyMeta); + + // find earliest key creation time, as wallet birthday + if (!pwallet->nTimeFirstKey || + (keyMeta.nCreateTime < pwallet->nTimeFirstKey)) + pwallet->nTimeFirstKey = keyMeta.nCreateTime; } else if (strType == "defaultkey") { @@ -349,13 +382,23 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, { int64 nIndex; ssKey >> nIndex; + CKeyPool keypool; + ssValue >> keypool; pwallet->setKeyPool.insert(nIndex); + + // If no metadata exists yet, create a default with the pool key's + // creation time. Note that this may be overwritten by actually + // stored metadata for that key later, which is fine. + CKeyID keyid = keypool.vchPubKey.GetID(); + if (pwallet->mapKeyMetadata.count(keyid) == 0) + pwallet->mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); + } else if (strType == "version") { - ssValue >> nFileVersion; - if (nFileVersion == 10300) - nFileVersion = 300; + ssValue >> wss.nFileVersion; + if (wss.nFileVersion == 10300) + wss.nFileVersion = 300; } else if (strType == "cscript") { @@ -389,10 +432,7 @@ static bool IsKeyType(string strType) DBErrors CWalletDB::LoadWallet(CWallet* pwallet) { pwallet->vchDefaultKey = CPubKey(); - int nFileVersion = 0; - vector vWalletUpgrade; - bool fIsEncrypted = false; - bool fAnyUnordered = false; + CWalletScanState wss; bool fNoncriticalErrors = false; DBErrors result = DB_LOAD_OK; @@ -430,8 +470,7 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) // Try to be tolerant of single corrupt records: string strType, strErr; - if (!ReadKeyValue(pwallet, ssKey, ssValue, nFileVersion, - vWalletUpgrade, fIsEncrypted, fAnyUnordered, strType, strErr)) + if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr)) { // losing keys is considered a catastrophic error, anything else // we assume the user can live with: @@ -464,19 +503,27 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) if (result != DB_LOAD_OK) return result; - printf("nFileVersion = %d\n", nFileVersion); + printf("nFileVersion = %d\n", wss.nFileVersion); + + printf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n", + wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys); - BOOST_FOREACH(uint256 hash, vWalletUpgrade) + // nTimeFirstKey is only reliable if all keys have metadata + if ((wss.nKeys + wss.nCKeys) != wss.nKeyMeta) + pwallet->nTimeFirstKey = 1; // 0 would be considered 'no value' + + + BOOST_FOREACH(uint256 hash, wss.vWalletUpgrade) WriteTx(hash, pwallet->mapWallet[hash]); // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc: - if (fIsEncrypted && (nFileVersion == 40000 || nFileVersion == 50000)) + if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000)) return DB_NEED_REWRITE; - if (nFileVersion < CLIENT_VERSION) // Update + if (wss.nFileVersion < CLIENT_VERSION) // Update WriteVersion(CLIENT_VERSION); - if (fAnyUnordered) + if (wss.fAnyUnordered) result = ReorderTransactions(pwallet); return result; @@ -632,10 +679,7 @@ bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys) return false; } CWallet dummyWallet; - int nFileVersion = 0; - vector vWalletUpgrade; - bool fIsEncrypted = false; - bool fAnyUnordered = false; + CWalletScanState wss; DbTxn* ptxn = dbenv.TxnBegin(); BOOST_FOREACH(CDBEnv::KeyValPair& row, salvagedData) @@ -646,9 +690,7 @@ bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys) CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); string strType, strErr; bool fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, - nFileVersion, vWalletUpgrade, - fIsEncrypted, fAnyUnordered, - strType, strErr); + wss, strType, strErr); if (!IsKeyType(strType)) continue; if (!fReadOK) diff --git a/src/walletdb.h b/src/walletdb.h index a3e779a..748bee5 100644 --- a/src/walletdb.h +++ b/src/walletdb.h @@ -23,6 +23,38 @@ enum DBErrors DB_NEED_REWRITE }; +class CKeyMetadata +{ +public: + static const int CURRENT_VERSION=1; + int nVersion; + int64 nCreateTime; // 0 means unknown + + CKeyMetadata() + { + SetNull(); + } + CKeyMetadata(int64 nCreateTime_) + { + nVersion = CKeyMetadata::CURRENT_VERSION; + nCreateTime = nCreateTime_; + } + + IMPLEMENT_SERIALIZE + ( + READWRITE(this->nVersion); + nVersion = this->nVersion; + READWRITE(nCreateTime); + ) + + void SetNull() + { + nVersion = CKeyMetadata::CURRENT_VERSION; + nCreateTime = 0; + } +}; + + /** Access to the wallet database (wallet.dat) */ class CWalletDB : public CDB { @@ -50,15 +82,26 @@ public: return Erase(std::make_pair(std::string("tx"), hash)); } - bool WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey) + bool WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, int64 nCreateTime) { nWalletDBUpdated++; + + CKeyMetadata keyMeta(nCreateTime); + if(!Write(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta, false)) + return false; + return Write(std::make_pair(std::string("key"), vchPubKey.Raw()), vchPrivKey, false); } - bool WriteCryptedKey(const CPubKey& vchPubKey, const std::vector& vchCryptedSecret, bool fEraseUnencryptedKey = true) + bool WriteCryptedKey(const CPubKey& vchPubKey, const std::vector& vchCryptedSecret, int64 nCreateTime) { nWalletDBUpdated++; + bool fEraseUnencryptedKey = true; + + CKeyMetadata keyMeta(nCreateTime); + if(!Write(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta, false)) + return false; + if (!Write(std::make_pair(std::string("ckey"), vchPubKey.Raw()), vchCryptedSecret, false)) return false; if (fEraseUnencryptedKey) -- 1.7.1