changeset 373:3b7f0f07a1f8 draft

New RPC command: listaccounts. New RPC setting -rpctimeout. And listtransactions '*' git-svn-id: https://bitcoin.svn.sourceforge.net/svnroot/bitcoin/trunk@203 1a98c847-1fd6-4fd8-948a-caf3550aa51b
author gavinandresen <gavinandresen@1a98c847-1fd6-4fd8-948a-caf3550aa51b>
date Thu, 16 Dec 2010 01:06:03 +0000
parents 71ca5b70e790
children 545d81f34196 a23c03d73768
files db.cpp db.h main.cpp main.h rpc.cpp serialize.h ui.cpp
diffstat 7 files changed, 281 insertions(+), 139 deletions(-) [+]
line wrap: on
line diff
--- a/db.cpp
+++ b/db.cpp
@@ -592,9 +592,9 @@
     return Write(make_pair(string("acc"), strAccount), account);
 }
 
-bool CWalletDB::WriteAccountingEntry(const string& strAccount, const CAccountingEntry& acentry)
+bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry)
 {
-    return Write(make_tuple(string("acentry"), strAccount, ++nAccountingEntryNumber), acentry);
+    return Write(make_tuple(string("acentry"), acentry.strAccount, ++nAccountingEntryNumber), acentry);
 }
 
 int64 CWalletDB::GetAccountCreditDebit(const string& strAccount)
@@ -613,6 +613,8 @@
 {
     int64 nCreditDebit = 0;
 
+    bool fAllAccounts = (strAccount == "*");
+
     Dbc* pcursor = GetCursor();
     if (!pcursor)
         throw runtime_error("CWalletDB::ListAccountCreditDebit() : cannot create DB cursor");
@@ -622,7 +624,7 @@
         // Read next record
         CDataStream ssKey;
         if (fFlags == DB_SET_RANGE)
-            ssKey << make_tuple(string("acentry"), strAccount, uint64(0));
+            ssKey << make_tuple(string("acentry"), (fAllAccounts? string("") : strAccount), uint64(0));
         CDataStream ssValue;
         int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
         fFlags = DB_NEXT;
@@ -639,12 +641,11 @@
         ssKey >> strType;
         if (strType != "acentry")
             break;
-        string strAccountName;
-        ssKey >> strAccountName;
-        if (strAccountName != strAccount)
+        CAccountingEntry acentry;
+        ssKey >> acentry.strAccount;
+        if (!fAllAccounts && acentry.strAccount != strAccount)
             break;
 
-        CAccountingEntry acentry;
         ssValue >> acentry;
         entries.push_back(acentry);
     }
@@ -652,6 +653,7 @@
     pcursor->close();
 }
 
+
 bool CWalletDB::LoadWallet()
 {
     vchDefaultKey.clear();
--- a/db.h
+++ b/db.h
@@ -433,7 +433,7 @@
 
     bool ReadAccount(const string& strAccount, CAccount& account);
     bool WriteAccount(const string& strAccount, const CAccount& account);
-    bool WriteAccountingEntry(const string& strAccount, const CAccountingEntry& acentry);
+    bool WriteAccountingEntry(const CAccountingEntry& acentry);
     int64 GetAccountCreditDebit(const string& strAccount);
     void ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& acentries);
 
--- a/main.cpp
+++ b/main.cpp
@@ -394,6 +394,63 @@
     return nRequests;
 }
 
+void CWalletTx::GetAmounts(int64& nGenerated, list<pair<string, int64> >& listReceived,
+                           int64& nSent, int64& nFee, string& strSentAccount) const
+{
+    nGenerated = nSent = nFee = 0;
+
+    if (IsCoinBase())
+    {
+        if (GetBlocksToMaturity() == 0)
+            nGenerated = GetCredit();
+        return;
+    }
+
+    // Received.  Standard client will never generate a send-to-multiple-recipients,
+    // but non-standard clients might (so return a list of address/amount pairs)
+    foreach(const CTxOut& txout, vout)
+    {
+        vector<unsigned char> vchPubKey;
+        if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey))
+            listReceived.push_back(make_pair(PubKeyToAddress(vchPubKey), txout.nValue));
+    }
+
+    // Sent
+    int64 nDebit = GetDebit();
+    if (nDebit > 0)
+    {
+        int64 nValueOut = GetValueOut();
+        nFee = nDebit - nValueOut;
+        nSent = nValueOut - GetChange();
+        strSentAccount = strFromAccount;
+    }
+}
+
+void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nGenerated, int64& nReceived, 
+                                  int64& nSent, int64& nFee) const
+{
+    nGenerated = nReceived = nSent = nFee = 0;
+
+    int64 allGenerated, allSent, allFee;
+    allGenerated = allSent = allFee = 0;
+    string strSentAccount;
+    list<pair<string, int64> > listReceived;
+    GetAmounts(allGenerated, listReceived, allSent, allFee, strSentAccount);
+
+    if (strAccount == "")
+        nGenerated = allGenerated;
+    if (strAccount == strSentAccount)
+    {
+        nSent = allSent;
+        nFee = allFee;
+    }
+    CRITICAL_BLOCK(cs_mapAddressBook)
+    {
+        foreach(const PAIRTYPE(string,int64)& r, listReceived)
+            if (mapAddressBook.count(r.first) && mapAddressBook[r.first] == strAccount)
+                nReceived += r.second;
+    }
+}
 
 
 
--- a/main.h
+++ b/main.h
@@ -873,36 +873,11 @@
         return nChangeCached;
     }
 
-    void GetAccountAmounts(string strAccount, const set<CScript>& setPubKey,
-                           int64& nGenerated, int64& nReceived, int64& nSent, int64& nFee) const
-    {
-        nGenerated = nReceived = nSent = nFee = 0;
-
-        // Generated blocks count to account ""
-        if (IsCoinBase())
-        {
-            if (strAccount == "" && GetBlocksToMaturity() == 0)
-                nGenerated = GetCredit();
-            return;
-        }
+    void GetAmounts(int64& nGenerated, list<pair<string /* address */, int64> >& listReceived,
+                    int64& nSent, int64& nFee, string& strSentAccount) const;
 
-        // Received
-        foreach(const CTxOut& txout, vout)
-            if (setPubKey.count(txout.scriptPubKey))
-                nReceived += txout.nValue;
-
-        // Sent
-        if (strFromAccount == strAccount)
-        {
-            int64 nDebit = GetDebit();
-            if (nDebit > 0)
-            {
-                int64 nValueOut = GetValueOut();
-                nFee = nDebit - nValueOut;
-                nSent = nValueOut - GetChange();
-            }
-        }
-    }
+    void GetAccountAmounts(const string& strAccount, int64& nGenerated, int64& nReceived, 
+                           int64& nSent, int64& nFee) const;
 
     bool IsFromMe() const
     {
@@ -1695,6 +1670,7 @@
 class CAccountingEntry
 {
 public:
+    string strAccount;
     int64 nCreditDebit;
     int64 nTime;
     string strOtherAccount;
@@ -1709,6 +1685,7 @@
     {
         nCreditDebit = 0;
         nTime = 0;
+        strAccount.clear();
         strOtherAccount.clear();
         strComment.clear();
     }
@@ -1717,6 +1694,7 @@
     (
         if (!(nType & SER_GETHASH))
             READWRITE(nVersion);
+        // Note: strAccount is serialized as part of the key, not here.
         READWRITE(nCreditDebit);
         READWRITE(nTime);
         READWRITE(strOtherAccount);
--- a/rpc.cpp
+++ b/rpc.cpp
@@ -84,7 +84,13 @@
         entry.push_back(Pair(item.first, item.second));
 }
 
-
+string AccountFromValue(const Value& value)
+{
+    string strAccount = value.get_str();
+    if (strAccount == "*")
+        throw JSONRPCError(-11, "Invalid account name");
+    return strAccount;
+}
 
 
 
@@ -296,7 +302,7 @@
     // Parse the account first so we don't generate a key if there's an error
     string strAccount;
     if (params.size() > 0)
-        strAccount = params[0].get_str();
+        strAccount = AccountFromValue(params[0]);
 
     // Generate a new key that is added to wallet
     string strAddress = PubKeyToAddress(GetKeyFromKeyPool());
@@ -314,7 +320,7 @@
             "Returns the current bitcoin address for receiving payments to this account.");
 
     // Parse the account first so we don't generate a key if there's an error
-    string strAccount = params[0].get_str();
+    string strAccount = AccountFromValue(params[0]);
 
     CRITICAL_BLOCK(cs_mapWallet)
     {
@@ -365,7 +371,7 @@
     string strAddress = params[0].get_str();
     string strAccount;
     if (params.size() > 1)
-        strAccount = params[1].get_str();
+        strAccount = AccountFromValue(params[1]);
 
     SetAddressBookName(strAddress, strAccount);
     return Value::null;
@@ -399,7 +405,7 @@
             "getaddressesbyaccount <account>\n"
             "Returns the list of addresses for the given account.");
 
-    string strAccount = params[0].get_str();
+    string strAccount = AccountFromValue(params[0]);
 
     // Find all addresses that have the given account
     Array ret;
@@ -436,7 +442,7 @@
     // Wallet comments
     CWalletTx wtx;
     if (params.size() > 2 && params[2].type() != null_type && !params[2].get_str().empty())
-        wtx.mapValue["message"] = params[2].get_str();
+        wtx.mapValue["comment"] = params[2].get_str();
     if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty())
         wtx.mapValue["to"]      = params[3].get_str();
 
@@ -522,7 +528,7 @@
         nMinDepth = params[1].get_int();
 
     // Get the set of pub keys that have the label
-    string strAccount = params[0].get_str();
+    string strAccount = AccountFromValue(params[0]);
     set<CScript> setPubKey;
     GetAccountPubKeys(strAccount, setPubKey);
 
@@ -549,9 +555,6 @@
 
 int64 GetAccountBalance(CWalletDB& walletdb, const string& strAccount, int nMinDepth)
 {
-    set<CScript> setPubKey;
-    GetAccountPubKeys(strAccount, setPubKey);
-
     int64 nBalance = 0;
     CRITICAL_BLOCK(cs_mapWallet)
     {
@@ -563,7 +566,7 @@
                 continue;
 
             int64 nGenerated, nReceived, nSent, nFee;
-            wtx.GetAccountAmounts(strAccount, setPubKey, nGenerated, nReceived, nSent, nFee);
+            wtx.GetAccountAmounts(strAccount, nGenerated, nReceived, nSent, nFee);
 
             if (nReceived != 0 && wtx.GetDepthInMainChain() >= nMinDepth)
                 nBalance += nReceived;
@@ -595,7 +598,7 @@
     if (params.size() == 0)
         return ((double)GetBalance() / (double)COIN);
 
-    string strAccount = params[0].get_str();
+    string strAccount = AccountFromValue(params[0]);
     int nMinDepth = 1;
     if (params.size() > 1)
         nMinDepth = params[1].get_int();
@@ -613,8 +616,8 @@
             "move <fromaccount> <toaccount> <amount> [minconf=1] [comment]\n"
             "Move from one account in your wallet to another.");
 
-    string strFrom = params[0].get_str();
-    string strTo = params[1].get_str();
+    string strFrom = AccountFromValue(params[0]);
+    string strTo = AccountFromValue(params[1]);
     int64 nAmount = AmountFromValue(params[2]);
     int nMinDepth = 1;
     if (params.size() > 3)
@@ -647,19 +650,21 @@
 
         // Debit
         CAccountingEntry debit;
+        debit.strAccount = strFrom;
         debit.nCreditDebit = -nAmount;
         debit.nTime = nNow;
         debit.strOtherAccount = strTo;
         debit.strComment = strComment;
-        walletdb.WriteAccountingEntry(strFrom, debit);
+        walletdb.WriteAccountingEntry(debit);
 
         // Credit
         CAccountingEntry credit;
+        credit.strAccount = strTo;
         credit.nCreditDebit = nAmount;
         credit.nTime = nNow;
         credit.strOtherAccount = strFrom;
         credit.strComment = strComment;
-        walletdb.WriteAccountingEntry(strTo, credit);
+        walletdb.WriteAccountingEntry(credit);
 
         walletdb.TxnCommit();
     }
@@ -674,7 +679,7 @@
             "sendfrom <fromaccount> <tobitcoinaddress> <amount> [minconf=1] [comment] [comment-to]\n"
             "<amount> is a real and is rounded to the nearest 0.01");
 
-    string strAccount = params[0].get_str();
+    string strAccount = AccountFromValue(params[0]);
     string strAddress = params[1].get_str();
     int64 nAmount = AmountFromValue(params[2]);
     int nMinDepth = 1;
@@ -684,7 +689,7 @@
     CWalletTx wtx;
     wtx.strFromAccount = strAccount;
     if (params.size() > 4 && params[4].type() != null_type && !params[4].get_str().empty())
-        wtx.mapValue["message"] = params[4].get_str();
+        wtx.mapValue["comment"] = params[4].get_str();
     if (params.size() > 5 && params[5].type() != null_type && !params[5].get_str().empty())
         wtx.mapValue["to"]      = params[5].get_str();
 
@@ -849,97 +854,180 @@
     return ListReceived(params, true);
 }
 
-void ListAccountTransactions(CWalletDB& walletdb, const string& strAccount, int nMinDepth, multimap<int64, Object>& ret)
+void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDepth, Array& ret)
 {
-    set<CScript> setPubKey;
-    GetAccountPubKeys(strAccount, setPubKey);
+    int64 nGenerated, nSent, nFee;
+    string strSentAccount;
+    list<pair<string, int64> > listReceived;
+    wtx.GetAmounts(nGenerated, listReceived, nSent, nFee, strSentAccount);
 
-    CRITICAL_BLOCK(cs_mapWallet)
+    bool fAllAccounts = (strAccount == string("*"));
+
+    // Generated blocks assigned to account ""
+    if (nGenerated != 0 && (fAllAccounts || strAccount == ""))
     {
-        // Wallet: generate/send/receive transactions
-        for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
-        {
-            const CWalletTx& wtx = (*it).second;
-            if (!wtx.IsFinal())
-                continue;
-
-            int64 nGenerated, nReceived, nSent, nFee;
-            wtx.GetAccountAmounts(strAccount, setPubKey, nGenerated, nReceived, nSent, nFee);
+        Object entry;
+        entry.push_back(Pair("account", string("")));
+        entry.push_back(Pair("category", "generate"));
+        entry.push_back(Pair("amount", ValueFromAmount(nGenerated)));
+        WalletTxToJSON(wtx, entry);
+        ret.push_back(entry);
+    }
 
-            // Generated blocks count to account ""
-            if (nGenerated != 0)
-            {
-                Object entry;
-                entry.push_back(Pair("category", "generate"));
-                entry.push_back(Pair("amount", ValueFromAmount(nGenerated)));
-                WalletTxToJSON(wtx, entry);
-                ret.insert(make_pair(wtx.GetTxTime(), entry));
-            }
+    // Sent
+    if ((nSent != 0 || nFee != 0) && (fAllAccounts || strAccount == strSentAccount))
+    {
+        Object entry;
+        entry.push_back(Pair("account", strSentAccount));
+        entry.push_back(Pair("category", "send"));
+        entry.push_back(Pair("amount", ValueFromAmount(-nSent)));
+        entry.push_back(Pair("fee", ValueFromAmount(-nFee)));
+        WalletTxToJSON(wtx, entry);
+        ret.push_back(entry);
+    }
 
-            // Sent
-            if (nSent != 0 || nFee != 0)
-            {
-                Object entry;
-                entry.push_back(Pair("category", "send"));
-                entry.push_back(Pair("amount", ValueFromAmount(-nSent)));
-                entry.push_back(Pair("fee", ValueFromAmount(-nFee)));
-                WalletTxToJSON(wtx, entry);
-                ret.insert(make_pair(wtx.GetTxTime(), entry));
-            }
-
-            // Received
-            if (nReceived != 0 && wtx.GetDepthInMainChain() >= nMinDepth)
-            {
-                Object entry;
-                entry.push_back(Pair("category", "receive"));
-                entry.push_back(Pair("amount", ValueFromAmount(nReceived)));
-                WalletTxToJSON(wtx, entry);
-                ret.insert(make_pair(wtx.GetTxTime(), entry));
-            }
+    // Received
+    if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth)
+        CRITICAL_BLOCK(cs_mapAddressBook)
+        {
+            foreach(const PAIRTYPE(string, int64)& r, listReceived)
+                if (mapAddressBook.count(r.first) && (fAllAccounts || r.first == strAccount))
+                {
+                    Object entry;
+                    entry.push_back(Pair("account", r.first));
+                    entry.push_back(Pair("category", "receive"));
+                    entry.push_back(Pair("amount", ValueFromAmount(r.second)));
+                    WalletTxToJSON(wtx, entry);
+                    ret.push_back(entry);
+                }
         }
 
-        // Internal accounting entries
-        list<CAccountingEntry> acentries;
-        walletdb.ListAccountCreditDebit(strAccount, acentries);
-        foreach (const CAccountingEntry& acentry, acentries)
-        {
-            Object entry;
-            entry.push_back(Pair("category", "move"));
-            entry.push_back(Pair("amount", ValueFromAmount(acentry.nCreditDebit)));
-            entry.push_back(Pair("otheraccount", acentry.strOtherAccount));
-            ret.insert(make_pair(acentry.nTime, entry));
-        }
+}
+
+void AcentryToJSON(const CAccountingEntry& acentry, const string& strAccount, Array& ret)
+{
+    bool fAllAccounts = (strAccount == string("*"));
+
+    if (fAllAccounts || acentry.strAccount == strAccount)
+    {
+        Object entry;
+        entry.push_back(Pair("account", acentry.strAccount));
+        entry.push_back(Pair("category", "move"));
+        entry.push_back(Pair("amount", ValueFromAmount(acentry.nCreditDebit)));
+        entry.push_back(Pair("otheraccount", acentry.strOtherAccount));
+        entry.push_back(Pair("comment", acentry.strComment));
+        ret.push_back(entry);
     }
 }
 
 Value listtransactions(const Array& params, bool fHelp)
 {
-    if (fHelp || params.size() < 1 || params.size() > 2)
+    if (fHelp || params.size() > 2)
         throw runtime_error(
-            "listtransactions <account> [count=10]\n"
+            "listtransactions [account] [count=10]\n"
             "Returns up to [count] most recent transactions for account <account>.");
 
-    string strAccount = params[0].get_str();
+    string strAccount = "*";
+    if (params.size() > 0)
+        strAccount = params[0].get_str();
     int nCount = 10;
     if (params.size() > 1)
         nCount = params[1].get_int();
 
+    Array ret;
     CWalletDB walletdb;
-    multimap<int64, Object> mapByTime;  // keys are transaction time
-    ListAccountTransactions(walletdb, strAccount, 0, mapByTime);
+
+    CRITICAL_BLOCK(cs_mapWallet)
+    {
+        // Firs: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap:
+        typedef pair<CWalletTx*, CAccountingEntry*> TxPair;
+        typedef multimap<int64, TxPair > TxItems;
+        TxItems txByTime;
+
+        for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+        {
+            CWalletTx* wtx = &((*it).second);
+            txByTime.insert(make_pair(wtx->GetTxTime(), TxPair(wtx, 0)));
+        }
+        list<CAccountingEntry> acentries;
+        walletdb.ListAccountCreditDebit(strAccount, acentries);
+        foreach(CAccountingEntry& entry, acentries)
+        {
+            txByTime.insert(make_pair(entry.nTime, TxPair(0, &entry)));
+        }
+
+        // Now: iterate backwards until we have nCount items to return:
+        for (TxItems::reverse_iterator it = txByTime.rbegin(); it != txByTime.rend(); ++it)
+        {
+            CWalletTx *const pwtx = (*it).second.first;
+            if (pwtx != 0)
+                ListTransactions(*pwtx, strAccount, 0, ret);
+            CAccountingEntry *const pacentry = (*it).second.second;
+            if (pacentry != 0)
+                AcentryToJSON(*pacentry, strAccount, ret);
 
-    // Return only last nCount items:
-    int nToErase = mapByTime.size()-nCount;
-    if (nToErase > 0)
+            if (ret.size() >= nCount) break;
+        }
+        // ret is now newest to oldest
+    }
+    
+    // Make sure we return only last nCount items (sends-to-self might give us an extra):
+    if (ret.size() > nCount)
     {
-        multimap<int64, Object>::iterator end = mapByTime.begin();
-        std::advance(end, nToErase);
-        mapByTime.erase(mapByTime.begin(), end);
+        Array::iterator last = ret.begin();
+        std::advance(last, nCount);
+        ret.erase(last, ret.end());
+    }
+    std::reverse(ret.begin(), ret.end()); // oldest to newest
+
+    return ret;
+}
+
+Value listaccounts(const Array& params, bool fHelp)
+{
+    if (fHelp || params.size() > 1)
+        throw runtime_error(
+            "listaccounts [minconf=1]\n"
+            "Returns Object that has account names as keys, account balances as values.");
+
+    int nMinDepth = 1;
+    if (params.size() > 1)
+        nMinDepth = params[1].get_int();
+
+    map<string, int64> mapAccountBalances;
+    CRITICAL_BLOCK(cs_mapWallet)
+    CRITICAL_BLOCK(cs_mapAddressBook)
+    {
+        foreach(const PAIRTYPE(string, string)& entry, mapAddressBook)
+            mapAccountBalances[entry.second] = 0;
+
+        for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+        {
+            const CWalletTx& wtx = (*it).second;
+            int64 nGenerated, nSent, nFee;
+            string strSentAccount;
+            list<pair<string, int64> > listReceived;
+            wtx.GetAmounts(nGenerated, listReceived, nSent, nFee, strSentAccount);
+            mapAccountBalances[strSentAccount] -= nSent+nFee;
+            if (wtx.GetDepthInMainChain() >= nMinDepth)
+            {
+                mapAccountBalances[""] += nGenerated;
+                foreach(const PAIRTYPE(string, int64)& r, listReceived)
+                    if (mapAddressBook.count(r.first))
+                        mapAccountBalances[mapAddressBook[r.first]] += r.second;
+            }
+        }
     }
 
-    Array ret;
-    foreach(const PAIRTYPE(int64, Object)& item, mapByTime)
-        ret.push_back(item.second);
+    list<CAccountingEntry> acentries;
+    CWalletDB().ListAccountCreditDebit("*", acentries);
+    foreach(const CAccountingEntry& entry, acentries)
+        mapAccountBalances[entry.strAccount] += entry.nCreditDebit;
+
+    Object ret;
+    foreach(const PAIRTYPE(string, int64)& accountBalance, mapAccountBalances) {
+        ret.push_back(Pair(accountBalance.first, ValueFromAmount(accountBalance.second)));
+    }
     return ret;
 }
 
@@ -1175,6 +1263,7 @@
     make_pair("gettransaction",        &gettransaction),
     make_pair("listtransactions",      &listtransactions),
     make_pair("getwork",               &getwork),
+    make_pair("listaccounts",          &listaccounts),
 };
 map<string, rpcfn_type> mapCallTable(pCallTable, pCallTable + sizeof(pCallTable)/sizeof(pCallTable[0]));
 
@@ -1247,7 +1336,7 @@
             "Server: bitcoin-json-rpc\r\n"
             "WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n"
             "Content-Type: text/html\r\n"
-            "Content-Length: 311\r\n"
+            "Content-Length: 296\r\n"
             "\r\n"
             "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n"
             "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n"
@@ -1421,6 +1510,17 @@
     return write_string(Value(reply), false) + "\n";
 }
 
+void ErrorReply(std::ostream& stream, const Object& objError, const Value& id)
+{
+    // Send error reply from json-rpc error object
+    int nStatus = 500;
+    int code = find_value(objError, "code").get_int();
+    if (code == -32600) nStatus = 400;
+    else if (code == -32601) nStatus = 404;
+    string strReply = JSONRPCReply(Value::null, objError, id);
+    stream << HTTPReply(nStatus, strReply) << std::flush;
+}
+
 bool ClientAllowed(const string& strAddress)
 {
     if (strAddress == asio::ip::address_v4::loopback().to_string())
@@ -1581,10 +1681,16 @@
         if (!ClientAllowed(peer.address().to_string()))
             continue;
 
-        // Receive request
         map<string, string> mapHeaders;
         string strRequest;
-        ReadHTTP(stream, mapHeaders, strRequest);
+
+        boost::thread api_caller(ReadHTTP, ref(stream), ref(mapHeaders), ref(strRequest));
+        if (!api_caller.timed_join(boost::posix_time::seconds(GetArg("-rpctimeout", 30))))
+        {   // Timed out:
+            acceptor.cancel();
+            printf("ThreadRPCServer ReadHTTP timeout\n");
+            continue;
+        }
 
         // Check authorization
         if (mapHeaders.count("Authorization") == 0)
@@ -1656,26 +1762,16 @@
             }
             catch (std::exception& e)
             {
-                // Send error reply from method
-                string strReply = JSONRPCReply(Value::null, JSONRPCError(-1, e.what()), id);
-                stream << HTTPReply(500, strReply) << std::flush;
+                ErrorReply(stream, JSONRPCError(-1, e.what()), id);
             }
         }
         catch (Object& objError)
         {
-            // Send error reply from json-rpc error object
-            int nStatus = 500;
-            int code = find_value(objError, "code").get_int();
-            if (code == -32600) nStatus = 400;
-            else if (code == -32601) nStatus = 404;
-            string strReply = JSONRPCReply(Value::null, objError, id);
-            stream << HTTPReply(nStatus, strReply) << std::flush;
+            ErrorReply(stream, objError, id);
         }
         catch (std::exception& e)
         {
-            // Send error reply from other json-rpc parsing errors
-            string strReply = JSONRPCReply(Value::null, JSONRPCError(-32700, e.what()), id);
-            stream << HTTPReply(500, strReply) << std::flush;
+            ErrorReply(stream, JSONRPCError(-32700, e.what()), id);
         }
     }
 }
@@ -1812,6 +1908,7 @@
         if (strMethod == "sendfrom"               && n > 2) ConvertTo<double>(params[2]);
         if (strMethod == "sendfrom"               && n > 3) ConvertTo<boost::int64_t>(params[3]);
         if (strMethod == "listtransactions"       && n > 1) ConvertTo<boost::int64_t>(params[1]);
+        if (strMethod == "listaccounts"           && n > 1) ConvertTo<boost::int64_t>(params[1]);
 
         // Execute
         Object reply = CallRPC(strMethod, params);
--- a/serialize.h
+++ b/serialize.h
@@ -25,7 +25,7 @@
 class CAutoFile;
 static const unsigned int MAX_SIZE = 0x02000000;
 
-static const int VERSION = 31901;
+static const int VERSION = 31902;
 static const char* pszSubVer = "";
 
 
--- a/ui.cpp
+++ b/ui.cpp
@@ -746,6 +746,12 @@
                         strDescription += " - ";
                     strDescription += mapValue["message"];
                 }
+                else if (!mapValue["comment"].empty())
+                {
+                    if (!strDescription.empty())
+                        strDescription += " - ";
+                    strDescription += mapValue["comment"];
+                }
 
                 int64 nValue = txout.nValue;
                 if (nTxFee > 0)
@@ -1405,6 +1411,8 @@
         //
         if (!wtx.mapValue["message"].empty())
             strHTML += string() + "<br><b>" + _("Message:") + "</b><br>" + HtmlEscape(wtx.mapValue["message"], true) + "<br>";
+        if (!wtx.mapValue["comment"].empty())
+            strHTML += string() + "<br><b>" + _("Comment:") + "</b><br>" + HtmlEscape(wtx.mapValue["comment"], true) + "<br>";
 
         if (wtx.IsCoinBase())
             strHTML += string() + "<br>" + _("Generated coins must wait 120 blocks before they can be spent.  When you generated this block, it was broadcast to the network to be added to the block chain.  If it fails to get into the chain, it will change to \"not accepted\" and not be spendable.  This may occasionally happen if another node generates a block within a few seconds of yours.") + "<br>";