changeset 282:9a20e4a6d6bb draft

key pool for safer wallet backup git-svn-id: https://bitcoin.svn.sourceforge.net/svnroot/bitcoin/trunk@163 1a98c847-1fd6-4fd8-948a-caf3550aa51b
author s_nakamoto <s_nakamoto@1a98c847-1fd6-4fd8-948a-caf3550aa51b>
date Sat, 09 Oct 2010 19:33:35 +0000
parents 0b94bb871b31
children ba794fc90b7d 4aadb84ec62c
files db.cpp db.h irc.cpp main.cpp main.h rpc.cpp serialize.h ui.cpp
diffstat 8 files changed, 198 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/db.cpp
+++ b/db.cpp
@@ -576,6 +576,9 @@
 // CWalletDB
 //
 
+static set<int64> setKeyPool;
+static CCriticalSection cs_setKeyPool;
+
 bool CWalletDB::LoadWallet()
 {
     vchDefaultKey.clear();
@@ -654,6 +657,12 @@
             {
                 ssValue >> vchDefaultKey;
             }
+            else if (strType == "pool")
+            {
+                int64 nIndex;
+                ssKey >> nIndex;
+                setKeyPool.insert(nIndex);
+            }
             else if (strType == "version")
             {
                 ssValue >> nFileVersion;
@@ -836,3 +845,59 @@
         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<unsigned char> CWalletDB::GetKeyFromKeyPool()
+{
+    int64 nIndex = 0;
+    CKeyPool keypool;
+    ReserveKeyFromKeyPool(nIndex, keypool);
+    KeepKey(nIndex);
+    return keypool.vchPubKey;
+}
--- a/db.h
+++ b/db.h
@@ -308,6 +308,35 @@
 
 
 
+class CKeyPool
+{
+public:
+    int64 nTime;
+    vector<unsigned char> vchPubKey;
+
+    CKeyPool()
+    {
+        nTime = GetTime();
+    }
+
+    CKeyPool(const vector<unsigned char>& 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 @@
     }
 
     bool LoadWallet();
+protected:
+    void ReserveKeyFromKeyPool(int64& nIndex, CKeyPool& keypool);
+    void KeepKey(int64 nIndex);
+    static void ReturnKey(int64 nIndex);
+    friend class CReserveKey;
+public:
+    vector<unsigned char> GetKeyFromKeyPool();
 };
 
 bool LoadWallet(bool& fFirstRunRet);
@@ -405,3 +441,48 @@
 {
     return CWalletDB().WriteName(strAddress, strName);
 }
+
+class CReserveKey
+{
+protected:
+    int64 nIndex;
+    vector<unsigned char> vchPubKey;
+public:
+    CReserveKey()
+    {
+        nIndex = -1;
+    }
+
+    ~CReserveKey()
+    {
+        ReturnKey();
+    }
+
+    vector<unsigned char> 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();
+    }
+};
--- a/irc.cpp
+++ b/irc.cpp
@@ -126,7 +126,7 @@
     }
 }
 
-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 @@
             return 2;
         if (psz3 && strLine.find(psz3) != -1)
             return 3;
+        if (psz4 && strLine.find(psz4) != -1)
+            return 4;
     }
 }
 
@@ -210,7 +212,7 @@
                 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;
--- a/main.cpp
+++ b/main.cpp
@@ -158,7 +158,8 @@
             if (txout.scriptPubKey == scriptDefaultKey)
             {
                 CWalletDB walletdb;
-                walletdb.WriteDefaultKey(GenerateNewKey());
+                vchDefaultKey = walletdb.GetKeyFromKeyPool();
+                walletdb.WriteDefaultKey(vchDefaultKey);
                 walletdb.WriteName(PubKeyToAddress(vchDefaultKey), "");
             }
         }
@@ -1493,15 +1494,6 @@
         (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 @@
 {
     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 @@
 
         // 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 @@
     if (mapArgs.count("-4way"))
         f4WaySSE2 = (mapArgs["-4way"] != "0");
 
-    CKey key;
-    key.MakeNewKey();
+    CReserveKey reservekey;
     CBigNum bnExtraNonce = 0;
     while (fGenerateBitcoins)
     {
@@ -2961,9 +2952,9 @@
         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 @@
                     {
                         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 @@
                 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 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 @@
                     //  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<unsigned char> 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 @@
 }
 
 // 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 @@
             // 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 @@
 {
     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 @@
         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();
--- a/main.h
+++ b/main.h
@@ -76,8 +76,8 @@
 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 @@
     {
         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
--- a/rpc.cpp
+++ b/rpc.cpp
@@ -275,7 +275,7 @@
         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;
--- a/serialize.h
+++ b/serialize.h
@@ -22,7 +22,7 @@
 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 @@
     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<char>& vchIn, int nTypeIn=0, int nVersionIn=VERSION) : vch(vchIn.begin(), vchIn.end())
+    CDataStream(const vector<char>& vchIn, int nTypeIn=SER_NETWORK, int nVersionIn=VERSION) : vch(vchIn.begin(), vchIn.end())
     {
         Init(nTypeIn, nVersionIn);
     }
 
-    CDataStream(const vector<unsigned char>& vchIn, int nTypeIn=0, int nVersionIn=VERSION) : vch((char*)&vchIn.begin()[0], (char*)&vchIn.end()[0])
+    CDataStream(const vector<unsigned char>& 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;
--- a/ui.cpp
+++ b/ui.cpp
@@ -1170,7 +1170,7 @@
     string strName = dialog.GetValue();
 
     // Generate new key
-    string strAddress = PubKeyToAddress(GenerateNewKey());
+    string strAddress = PubKeyToAddress(CWalletDB().GetKeyFromKeyPool());
 
     // Save
     SetAddressBookName(strAddress, strName);
@@ -1425,7 +1425,10 @@
                 if (txout.IsMine())
                     strHTML += "<b>Credit:</b> " + FormatMoney(txout.GetCredit()) + "<br>";
 
-            strHTML += "<b>Inputs:</b><br>";
+            strHTML += "<br><b>Transaction:</b><br>";
+            strHTML += HtmlEscape(wtx.ToString(), true);
+
+            strHTML += "<br><b>Inputs:</b><br>";
             CRITICAL_BLOCK(cs_mapWallet)
             {
                 foreach(const CTxIn& txin, wtx.vin)
@@ -1444,9 +1447,6 @@
                     }
                 }
             }
-
-            strHTML += "<br><hr><br><b>Transaction:</b><br>";
-            strHTML += HtmlEscape(wtx.ToString(), true);
         }
 
 
@@ -2245,9 +2245,9 @@
             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 @@
             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 @@
         strName = dialog.GetValue();
 
         // Generate new key
-        strAddress = PubKeyToAddress(GenerateNewKey());
+        strAddress = PubKeyToAddress(CWalletDB().GetKeyFromKeyPool());
     }
 
     // Add to list and select it