From 103849419a9c014a69c76b6f96e48b66cbc838ca Mon Sep 17 00:00:00 2001 From: s_nakamoto Date: Sat, 9 Oct 2010 19:33:35 +0000 Subject: [PATCH] key pool for safer wallet backup git-svn-id: https://bitcoin.svn.sourceforge.net/svnroot/bitcoin/trunk@163 1a98c847-1fd6-4fd8-948a-caf3550aa51b --- db.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++ db.h | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++ irc.cpp | 6 ++-- main.cpp | 61 ++++++++++++++++++---------------------- main.h | 6 ++-- rpc.cpp | 2 +- serialize.h | 16 +++++------ ui.cpp | 18 ++++++------ 8 files changed, 198 insertions(+), 57 deletions(-) diff --git a/db.cpp b/db.cpp index a7c18a6ff..336d5a5ec 100644 --- a/db.cpp +++ b/db.cpp @@ -576,6 +576,9 @@ bool LoadAddresses() // CWalletDB // +static set setKeyPool; +static CCriticalSection cs_setKeyPool; + bool CWalletDB::LoadWallet() { vchDefaultKey.clear(); @@ -654,6 +657,12 @@ bool CWalletDB::LoadWallet() { ssValue >> vchDefaultKey; } + else if (strType == "pool") + { + int64 nIndex; + ssKey >> nIndex; + setKeyPool.insert(nIndex); + } else if (strType == "version") { ssValue >> nFileVersion; @@ -836,3 +845,59 @@ void BackupWallet(const string& strDest) Sleep(100); } } + +void CWalletDB::ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool) +{ + nIndex = -1; + keypool.vchPubKey.clear(); + CRITICAL_BLOCK(cs_setKeyPool) + { + // Top up key pool + int64 nTargetSize = max(GetArg("-keypool", 100), (int64)0); + while (setKeyPool.size() < nTargetSize+1) + { + int64 nEnd = 1; + if (!setKeyPool.empty()) + nEnd = *(--setKeyPool.end()) + 1; + if (!Write(make_pair(string("pool"), nEnd), CKeyPool(GenerateNewKey()))) + throw runtime_error("ReserveKeyFromKeyPool() : writing generated key failed"); + setKeyPool.insert(nEnd); + printf("keypool added key %"PRI64d", size=%d\n", nEnd, setKeyPool.size()); + } + + // Get the oldest key + assert(!setKeyPool.empty()); + nIndex = *(setKeyPool.begin()); + setKeyPool.erase(setKeyPool.begin()); + if (!Read(make_pair(string("pool"), nIndex), keypool)) + throw runtime_error("ReserveKeyFromKeyPool() : read failed"); + if (!mapKeys.count(keypool.vchPubKey)) + throw runtime_error("ReserveKeyFromKeyPool() : unknown key in key pool"); + assert(!keypool.vchPubKey.empty()); + printf("keypool reserve %"PRI64d"\n", nIndex); + } +} + +void CWalletDB::KeepKey(int64 nIndex) +{ + // Remove from key pool + Erase(make_pair(string("pool"), nIndex)); + printf("keypool keep %"PRI64d"\n", nIndex); +} + +void CWalletDB::ReturnKey(int64 nIndex) +{ + // Return to key pool + CRITICAL_BLOCK(cs_setKeyPool) + setKeyPool.insert(nIndex); + printf("keypool return %"PRI64d"\n", nIndex); +} + +vector CWalletDB::GetKeyFromKeyPool() +{ + int64 nIndex = 0; + CKeyPool keypool; + ReserveKeyFromKeyPool(nIndex, keypool); + KeepKey(nIndex); + return keypool.vchPubKey; +} diff --git a/db.h b/db.h index 9b1f5e58b..dac277a01 100644 --- a/db.h +++ b/db.h @@ -308,6 +308,35 @@ bool LoadAddresses(); +class CKeyPool +{ +public: + int64 nTime; + vector vchPubKey; + + CKeyPool() + { + nTime = GetTime(); + } + + CKeyPool(const vector& vchPubKeyIn) + { + nTime = GetTime(); + vchPubKey = vchPubKeyIn; + } + + IMPLEMENT_SERIALIZE + ( + if (!(nType & SER_GETHASH)) + READWRITE(nVersion); + READWRITE(nTime); + READWRITE(vchPubKey); + ) +}; + + + + class CWalletDB : public CDB { public: @@ -396,6 +425,13 @@ public: } bool LoadWallet(); +protected: + void ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool); + void KeepKey(int64 nIndex); + static void ReturnKey(int64 nIndex); + friend class CReserveKey; +public: + vector GetKeyFromKeyPool(); }; bool LoadWallet(bool& fFirstRunRet); @@ -405,3 +441,48 @@ inline bool SetAddressBookName(const string& strAddress, const string& strName) { return CWalletDB().WriteName(strAddress, strName); } + +class CReserveKey +{ +protected: + int64 nIndex; + vector vchPubKey; +public: + CReserveKey() + { + nIndex = -1; + } + + ~CReserveKey() + { + ReturnKey(); + } + + vector GetReservedKey() + { + if (nIndex == -1) + { + CKeyPool keypool; + CWalletDB().ReserveKeyFromKeyPool(nIndex, keypool); + vchPubKey = keypool.vchPubKey; + } + assert(!vchPubKey.empty()); + return vchPubKey; + } + + void KeepKey() + { + if (nIndex != -1) + CWalletDB().KeepKey(nIndex); + nIndex = -1; + vchPubKey.clear(); + } + + void ReturnKey() + { + if (nIndex != -1) + CWalletDB::ReturnKey(nIndex); + nIndex = -1; + vchPubKey.clear(); + } +}; diff --git a/irc.cpp b/irc.cpp index a520173f1..0675d1861 100644 --- a/irc.cpp +++ b/irc.cpp @@ -126,7 +126,7 @@ bool RecvLineIRC(SOCKET hSocket, string& strLine) } } -int RecvUntil(SOCKET hSocket, const char* psz1, const char* psz2=NULL, const char* psz3=NULL) +int RecvUntil(SOCKET hSocket, const char* psz1, const char* psz2=NULL, const char* psz3=NULL, const char* psz4=NULL) { loop { @@ -141,6 +141,8 @@ int RecvUntil(SOCKET hSocket, const char* psz1, const char* psz2=NULL, const cha return 2; if (psz3 && strLine.find(psz3) != -1) return 3; + if (psz4 && strLine.find(psz4) != -1) + return 4; } } @@ -210,7 +212,7 @@ void ThreadIRCSeed2(void* parg) return; } - if (!RecvUntil(hSocket, "Found your hostname", "using your IP address instead", "Couldn't look up your hostname")) + if (!RecvUntil(hSocket, "Found your hostname", "using your IP address instead", "Couldn't look up your hostname", "ignoring hostname")) { closesocket(hSocket); hSocket = INVALID_SOCKET; diff --git a/main.cpp b/main.cpp index c03172ef1..7b04a1f63 100644 --- a/main.cpp +++ b/main.cpp @@ -158,7 +158,8 @@ bool AddToWallet(const CWalletTx& wtxIn) if (txout.scriptPubKey == scriptDefaultKey) { CWalletDB walletdb; - walletdb.WriteDefaultKey(GenerateNewKey()); + vchDefaultKey = walletdb.GetKeyFromKeyPool(); + walletdb.WriteDefaultKey(vchDefaultKey); walletdb.WriteName(PubKeyToAddress(vchDefaultKey), ""); } } @@ -1493,15 +1494,6 @@ bool CBlock::AcceptBlock() (nHeight == 74000 && hash != uint256("0x0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20"))) return error("AcceptBlock() : rejected by checkpoint lockin at %d", nHeight); - // Scanback checkpoint lockin - for (CBlockIndex* pindex = pindexPrev; pindex->nHeight >= 74000; pindex = pindex->pprev) - { - if (pindex->nHeight == 74000 && pindex->GetBlockHash() != uint256("0x0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20")) - return error("AcceptBlock() : rejected by scanback lockin at %d", pindex->nHeight); - if (pindex->nHeight == 74638 && pindex->GetBlockHash() == uint256("0x0000000000790ab3f22ec756ad43b6ab569abf0bddeb97c67a6f7b1470a7ec1c")) - return error("AcceptBlock() : rejected by scanback lockin at %d", pindex->nHeight); - } - // Write block to history file if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK))) return error("AcceptBlock() : out of disk space"); @@ -1961,7 +1953,7 @@ bool AlreadyHave(CTxDB& txdb, const CInv& inv) { switch (inv.type) { - case MSG_TX: return mapTransactions.count(inv.hash) || txdb.ContainsTx(inv.hash); + case MSG_TX: return mapTransactions.count(inv.hash) || mapOrphanTransactions.count(inv.hash) || txdb.ContainsTx(inv.hash); case MSG_BLOCK: return mapBlockIndex.count(inv.hash) || mapOrphanBlocks.count(inv.hash); } // Don't know what it is, just say we already got one @@ -2472,7 +2464,7 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Keep giving the same key to the same ip until they use it if (!mapReuseKey.count(pfrom->addr.ip)) - mapReuseKey[pfrom->addr.ip] = GenerateNewKey(); + mapReuseKey[pfrom->addr.ip] = CWalletDB().GetKeyFromKeyPool(); // Send back approval of order and pubkey to use CScript scriptPubKey; @@ -2933,8 +2925,7 @@ void BitcoinMiner() if (mapArgs.count("-4way")) f4WaySSE2 = (mapArgs["-4way"] != "0"); - CKey key; - key.MakeNewKey(); + CReserveKey reservekey; CBigNum bnExtraNonce = 0; while (fGenerateBitcoins) { @@ -2961,9 +2952,9 @@ void BitcoinMiner() CTransaction txNew; txNew.vin.resize(1); txNew.vin[0].prevout.SetNull(); - txNew.vin[0].scriptSig << nBits << ++bnExtraNonce; + txNew.vin[0].scriptSig << ++bnExtraNonce; txNew.vout.resize(1); - txNew.vout[0].scriptPubKey << key.GetPubKey() << OP_CHECKSIG; + txNew.vout[0].scriptPubKey << reservekey.GetReservedKey() << OP_CHECKSIG; // @@ -3113,10 +3104,8 @@ void BitcoinMiner() { if (pindexPrev == pindexBest) { - // Save key - if (!AddKey(key)) - return; - key.MakeNewKey(); + // Remove key from key pool + reservekey.KeepKey(); // Track how many getdata requests this block gets CRITICAL_BLOCK(cs_mapRequestCount) @@ -3183,7 +3172,10 @@ void BitcoinMiner() break; // Update nTime every few seconds - pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); + int64 nNewTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); + if (nNewTime != pblock->nTime && bnExtraNonce > 10) + bnExtraNonce = 0; + pblock->nTime = nNewTime; tmp.block.nTime = ByteReverse(pblock->nTime); } } @@ -3342,7 +3334,7 @@ bool SelectCoins(int64 nTargetValue, set& setCoinsRet) -bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CKey& keyRet, int64& nFeeRequiredRet) +bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRequiredRet) { nFeeRequiredRet = 0; CRITICAL_BLOCK(cs_main) @@ -3386,18 +3378,20 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CK // rediscover unknown transactions that were written with keys of ours to recover // post-backup change. - // New private key - if (keyRet.IsNull()) - keyRet.MakeNewKey(); + // Reserve a new key pair from key pool + vector vchPubKey = reservekey.GetReservedKey(); + assert(mapKeys.count(vchPubKey)); // Fill a vout to ourself, using same address type as the payment CScript scriptChange; if (scriptPubKey.GetBitcoinAddressHash160() != 0) - scriptChange.SetBitcoinAddress(keyRet.GetPubKey()); + scriptChange.SetBitcoinAddress(vchPubKey); else - scriptChange << keyRet.GetPubKey() << OP_CHECKSIG; + scriptChange << vchPubKey << OP_CHECKSIG; wtxNew.vout.push_back(CTxOut(nChange, scriptChange)); } + else + reservekey.ReturnKey(); // Fill a vout to the payee if (fChangeFirst) @@ -3440,7 +3434,7 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CK } // Call after CreateTransaction unless you want to abort -bool CommitTransaction(CWalletTx& wtxNew, const CKey& key) +bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) { CRITICAL_BLOCK(cs_main) { @@ -3452,9 +3446,8 @@ bool CommitTransaction(CWalletTx& wtxNew, const CKey& key) // maybe makes sense; please don't do it anywhere else. CWalletDB walletdb("r"); - // Add the change's private key to wallet - if (!key.IsNull() && !AddKey(key)) - throw runtime_error("CommitTransaction() : AddKey failed"); + // Take key pair from key pool so it won't be used again + reservekey.KeepKey(); // Add tx to wallet, because if it has change it's also ours, // otherwise just for transaction history. @@ -3496,9 +3489,9 @@ string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAs { CRITICAL_BLOCK(cs_main) { - CKey key; + CReserveKey reservekey; int64 nFeeRequired; - if (!CreateTransaction(scriptPubKey, nValue, wtxNew, key, nFeeRequired)) + if (!CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired)) { string strError; if (nValue + nFeeRequired > GetBalance()) @@ -3512,7 +3505,7 @@ string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAs if (fAskFee && !ThreadSafeAskFee(nFeeRequired, _("Sending..."), NULL)) return "ABORTED"; - if (!CommitTransaction(wtxNew, key)) + if (!CommitTransaction(wtxNew, reservekey)) return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); } MainFrameRepaint(); diff --git a/main.h b/main.h index f6993691e..d4293b13b 100644 --- a/main.h +++ b/main.h @@ -76,8 +76,8 @@ bool ProcessMessages(CNode* pfrom); bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv); bool SendMessages(CNode* pto, bool fSendTrickle); int64 GetBalance(); -bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CKey& keyRet, int64& nFeeRequiredRet); -bool CommitTransaction(CWalletTx& wtxNew, const CKey& key); +bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRequiredRet); +bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); bool BroadcastTransaction(CWalletTx& wtxNew); string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); string SendMoneyToBitcoinAddress(string strAddress, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); @@ -361,7 +361,7 @@ public: { if (scriptPubKey.size() < 6) return "CTxOut(error)"; - return strprintf("CTxOut(nValue=%"PRI64d".%08"PRI64d", scriptPubKey=%s)", nValue / COIN, nValue % COIN, scriptPubKey.ToString().substr(0,24).c_str()); + return strprintf("CTxOut(nValue=%"PRI64d".%08"PRI64d", scriptPubKey=%s)", nValue / COIN, nValue % COIN, scriptPubKey.ToString().substr(0,30).c_str()); } void print() const diff --git a/rpc.cpp b/rpc.cpp index 9a4757390..19ec95004 100644 --- a/rpc.cpp +++ b/rpc.cpp @@ -275,7 +275,7 @@ Value getnewaddress(const Array& params, bool fHelp) strLabel = params[0].get_str(); // Generate a new key that is added to wallet - string strAddress = PubKeyToAddress(GenerateNewKey()); + string strAddress = PubKeyToAddress(CWalletDB().GetKeyFromKeyPool()); SetAddressBookName(strAddress, strLabel); return strAddress; diff --git a/serialize.h b/serialize.h index 94fbe6397..fde83e7a8 100644 --- a/serialize.h +++ b/serialize.h @@ -22,7 +22,7 @@ class CDataStream; class CAutoFile; static const unsigned int MAX_SIZE = 0x02000000; -static const int VERSION = 31302; +static const int VERSION = 31303; static const char* pszSubVer = ""; @@ -725,39 +725,39 @@ public: typedef vector_type::const_iterator const_iterator; typedef vector_type::reverse_iterator reverse_iterator; - explicit CDataStream(int nTypeIn=0, int nVersionIn=VERSION) + explicit CDataStream(int nTypeIn=SER_NETWORK, int nVersionIn=VERSION) { Init(nTypeIn, nVersionIn); } - CDataStream(const_iterator pbegin, const_iterator pend, int nTypeIn=0, int nVersionIn=VERSION) : vch(pbegin, pend) + CDataStream(const_iterator pbegin, const_iterator pend, int nTypeIn=SER_NETWORK, int nVersionIn=VERSION) : vch(pbegin, pend) { Init(nTypeIn, nVersionIn); } #if !defined(_MSC_VER) || _MSC_VER >= 1300 - CDataStream(const char* pbegin, const char* pend, int nTypeIn=0, int nVersionIn=VERSION) : vch(pbegin, pend) + CDataStream(const char* pbegin, const char* pend, int nTypeIn=SER_NETWORK, int nVersionIn=VERSION) : vch(pbegin, pend) { Init(nTypeIn, nVersionIn); } #endif - CDataStream(const vector_type& vchIn, int nTypeIn=0, int nVersionIn=VERSION) : vch(vchIn.begin(), vchIn.end()) + CDataStream(const vector_type& vchIn, int nTypeIn=SER_NETWORK, int nVersionIn=VERSION) : vch(vchIn.begin(), vchIn.end()) { Init(nTypeIn, nVersionIn); } - CDataStream(const vector& vchIn, int nTypeIn=0, int nVersionIn=VERSION) : vch(vchIn.begin(), vchIn.end()) + CDataStream(const vector& vchIn, int nTypeIn=SER_NETWORK, int nVersionIn=VERSION) : vch(vchIn.begin(), vchIn.end()) { Init(nTypeIn, nVersionIn); } - CDataStream(const vector& vchIn, int nTypeIn=0, int nVersionIn=VERSION) : vch((char*)&vchIn.begin()[0], (char*)&vchIn.end()[0]) + CDataStream(const vector& vchIn, int nTypeIn=SER_NETWORK, int nVersionIn=VERSION) : vch((char*)&vchIn.begin()[0], (char*)&vchIn.end()[0]) { Init(nTypeIn, nVersionIn); } - void Init(int nTypeIn=0, int nVersionIn=VERSION) + void Init(int nTypeIn=SER_NETWORK, int nVersionIn=VERSION) { nReadPos = 0; nType = nTypeIn; diff --git a/ui.cpp b/ui.cpp index a82365f36..d72e1a00a 100644 --- a/ui.cpp +++ b/ui.cpp @@ -1170,7 +1170,7 @@ void CMainFrame::OnButtonNew(wxCommandEvent& event) string strName = dialog.GetValue(); // Generate new key - string strAddress = PubKeyToAddress(GenerateNewKey()); + string strAddress = PubKeyToAddress(CWalletDB().GetKeyFromKeyPool()); // Save SetAddressBookName(strAddress, strName); @@ -1425,7 +1425,10 @@ CTxDetailsDialog::CTxDetailsDialog(wxWindow* parent, CWalletTx wtx) : CTxDetails if (txout.IsMine()) strHTML += "Credit: " + FormatMoney(txout.GetCredit()) + "
"; - strHTML += "Inputs:
"; + strHTML += "
Transaction:
"; + strHTML += HtmlEscape(wtx.ToString(), true); + + strHTML += "
Inputs:
"; CRITICAL_BLOCK(cs_mapWallet) { foreach(const CTxIn& txin, wtx.vin) @@ -1444,9 +1447,6 @@ CTxDetailsDialog::CTxDetailsDialog(wxWindow* parent, CWalletTx wtx) : CTxDetails } } } - - strHTML += "


Transaction:
"; - strHTML += HtmlEscape(wtx.ToString(), true); } @@ -2245,9 +2245,9 @@ void CSendingDialog::OnReply2(CDataStream& vRecv) Error(_("Insufficient funds")); return; } - CKey key; + CReserveKey reservekey; int64 nFeeRequired; - if (!CreateTransaction(scriptPubKey, nPrice, wtx, key, nFeeRequired)) + if (!CreateTransaction(scriptPubKey, nPrice, wtx, reservekey, nFeeRequired)) { if (nPrice + nFeeRequired > GetBalance()) Error(strprintf(_("This is an oversized transaction that requires a transaction fee of %s"), FormatMoney(nFeeRequired).c_str())); @@ -2287,7 +2287,7 @@ void CSendingDialog::OnReply2(CDataStream& vRecv) return; // Commit - if (!CommitTransaction(wtx, key)) + if (!CommitTransaction(wtx, reservekey)) { Error(_("The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here.")); return; @@ -2565,7 +2565,7 @@ void CAddressBookDialog::OnButtonNew(wxCommandEvent& event) strName = dialog.GetValue(); // Generate new key - strAddress = PubKeyToAddress(GenerateNewKey()); + strAddress = PubKeyToAddress(CWalletDB().GetKeyFromKeyPool()); } // Add to list and select it