JSON methods: listtransactions, gettransaction, move, sendfrom and getbalance <account>
authorgavinandresen <gavinandresen@1a98c847-1fd6-4fd8-948a-caf3550aa51b>
Tue, 30 Nov 2010 18:58:11 +0000 (18:58 +0000)
committergavinandresen <gavinandresen@1a98c847-1fd6-4fd8-948a-caf3550aa51b>
Tue, 30 Nov 2010 18:58:11 +0000 (18:58 +0000)
git-svn-id: https://bitcoin.svn.sourceforge.net/svnroot/bitcoin/trunk@193 1a98c847-1fd6-4fd8-948a-caf3550aa51b

db.cpp
db.h
main.cpp
main.h
rpc.cpp
ui.cpp

diff --git a/db.cpp b/db.cpp
index 2833a8b..40998fb 100644 (file)
--- a/db.cpp
+++ b/db.cpp
@@ -597,11 +597,23 @@ bool CWalletDB::WriteAccountingEntry(const string& strAccount, const CAccounting
 
 int64 CWalletDB::GetAccountCreditDebit(const string& strAccount)
 {
+    list<CAccountingEntry> entries;
+    ListAccountCreditDebit(strAccount, entries);
+
+    int64 nCreditDebit = 0;
+    foreach (const CAccountingEntry& entry, entries)
+        nCreditDebit += entry.nCreditDebit;
+
+    return nCreditDebit;
+}
+
+void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& entries)
+{
     int64 nCreditDebit = 0;
 
     Dbc* pcursor = GetCursor();
     if (!pcursor)
-        throw runtime_error("CWalletDB::GetAccountCreditDebit() : cannot create DB cursor");
+        throw runtime_error("CWalletDB::ListAccountCreditDebit() : cannot create DB cursor");
     unsigned int fFlags = DB_SET_RANGE;
     loop
     {
@@ -617,7 +629,7 @@ int64 CWalletDB::GetAccountCreditDebit(const string& strAccount)
         else if (ret != 0)
         {
             pcursor->close();
-            throw runtime_error("CWalletDB::GetAccountCreditDebit() : error scanning DB");
+            throw runtime_error("CWalletDB::ListAccountCreditDebit() : error scanning DB");
         }
 
         // Unserialize
@@ -632,11 +644,10 @@ int64 CWalletDB::GetAccountCreditDebit(const string& strAccount)
 
         CAccountingEntry acentry;
         ssValue >> acentry;
-        nCreditDebit += acentry.nCreditDebit;
+        entries.push_back(acentry);
     }
 
     pcursor->close();
-    return nCreditDebit;
 }
 
 bool CWalletDB::LoadWallet()
diff --git a/db.h b/db.h
index 0f705a8..e3ffc40 100644 (file)
--- a/db.h
+++ b/db.h
@@ -435,6 +435,7 @@ public:
     bool WriteAccount(const string& strAccount, const CAccount& account);
     bool WriteAccountingEntry(const string& strAccount, const CAccountingEntry& acentry);
     int64 GetAccountCreditDebit(const string& strAccount);
+    void ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& acentries);
 
     bool LoadWallet();
 protected:
index acfcbc9..a1865a4 100644 (file)
--- a/main.cpp
+++ b/main.cpp
@@ -3492,7 +3492,7 @@ int64 GetBalance()
             CWalletTx* pcoin = &(*it).second;
             if (!pcoin->IsFinal() || pcoin->fSpent || !pcoin->IsConfirmed())
                 continue;
-            nTotal += pcoin->GetCredit(true);
+            nTotal += pcoin->GetCredit();
         }
     }
 
diff --git a/main.h b/main.h
index e5000d7..2c24eba 100644 (file)
--- a/main.h
+++ b/main.h
@@ -345,9 +345,25 @@ public:
     {
         if (!MoneyRange(nValue))
             throw runtime_error("CTxOut::GetCredit() : value out of range");
-        if (IsMine())
-            return nValue;
-        return 0;
+        return (IsMine() ? nValue : 0);
+    }
+
+    bool IsChange() const
+    {
+        // On a debit transaction, a txout that's mine but isn't in the address book is change
+        vector<unsigned char> vchPubKey;
+        if (ExtractPubKey(scriptPubKey, true, vchPubKey))
+            CRITICAL_BLOCK(cs_mapAddressBook)
+                if (!mapAddressBook.count(PubKeyToAddress(vchPubKey)))
+                    return true;
+        return false;
+    }
+
+    int64 GetChange() const
+    {
+        if (!MoneyRange(nValue))
+            throw runtime_error("CTxOut::GetChange() : value out of range");
+        return (IsChange() ? nValue : 0);
     }
 
     friend bool operator==(const CTxOut& a, const CTxOut& b)
@@ -520,6 +536,20 @@ public:
         return nCredit;
     }
 
+    int64 GetChange() const
+    {
+        if (IsCoinBase())
+            return 0;
+        int64 nChange = 0;
+        foreach(const CTxOut& txout, vout)
+        {
+            nChange += txout.GetChange();
+            if (!MoneyRange(nChange))
+                throw runtime_error("CTransaction::GetChange() : value out of range");
+        }
+        return nChange;
+    }
+
     int64 GetValueOut() const
     {
         int64 nValueOut = 0;
@@ -731,8 +761,10 @@ public:
     // memory only
     mutable char fDebitCached;
     mutable char fCreditCached;
+    mutable char fChangeCached;
     mutable int64 nDebitCached;
     mutable int64 nCreditCached;
+    mutable int64 nChangeCached;
 
     // memory only UI hints
     mutable unsigned int nTimeDisplayed;
@@ -768,8 +800,10 @@ public:
         strFromAccount.clear();
         fDebitCached = false;
         fCreditCached = false;
+        fChangeCached = false;
         nDebitCached = 0;
         nCreditCached = 0;
+        nChangeCached = 0;
         nTimeDisplayed = 0;
         nLinesDisplayed = 0;
         fConfirmedDisplayed = false;
@@ -808,7 +842,7 @@ public:
         return nDebitCached;
     }
 
-    int64 GetCredit(bool fUseCache=false) const
+    int64 GetCredit(bool fUseCache=true) const
     {
         // Must wait until coinbase is safely deep enough in the chain before valuing it
         if (IsCoinBase() && GetBlocksToMaturity() > 0)
@@ -822,11 +856,51 @@ public:
         return nCreditCached;
     }
 
+    int64 GetChange() const
+    {
+        if (fChangeCached)
+            return nChangeCached;
+        nChangeCached = CTransaction::GetChange();
+        fChangeCached = true;
+        return nChangeCached;
+    }
+
     bool IsFromMe() const
     {
         return (GetDebit() > 0);
     }
 
+    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;
+        }
+
+        // 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();
+            }
+        }
+    }
+
     bool IsConfirmed() const
     {
         // Quick answer in most cases
diff --git a/rpc.cpp b/rpc.cpp
index a7066ab..0d00f4c 100644 (file)
--- a/rpc.cpp
+++ b/rpc.cpp
@@ -71,6 +71,18 @@ int64 AmountFromValue(const Value& value)
     return nAmount;
 }
 
+Value ValueFromAmount(int64 amount)
+{
+    return (double)amount / (double)COIN;
+}
+
+void WalletTxToJSON(const CWalletTx& wtx, Object& entry)
+{
+    entry.push_back(Pair("confirmations", wtx.GetDepthInMainChain()));
+    entry.push_back(Pair("txid", wtx.GetHash().GetHex()));
+    foreach(const PAIRTYPE(string,string)& item, wtx.mapValue)
+        entry.push_back(Pair(item.first, item.second));
+}
 
 
 
@@ -435,27 +447,6 @@ Value sendtoaddress(const Array& params, bool fHelp)
 }
 
 
-Value listtransactions(const Array& params, bool fHelp)
-{
-    if (fHelp || params.size() > 2)
-        throw runtime_error(
-            "listtransactions [count=10] [includegenerated=false]\n"
-            "Returns up to [count] most recent transactions.");
-
-    int64 nCount = 10;
-    if (params.size() > 0)
-        nCount = params[0].get_int64();
-    bool fGenerated = false;
-    if (params.size() > 1)
-        fGenerated = params[1].get_bool();
-
-    Array ret;
-    //// not finished
-    ret.push_back("not implemented yet");
-    return ret;
-}
-
-
 Value getreceivedbyaddress(const Array& params, bool fHelp)
 {
     if (fHelp || params.size() < 1 || params.size() > 2)
@@ -497,21 +488,8 @@ Value getreceivedbyaddress(const Array& params, bool fHelp)
 }
 
 
-Value getreceivedbyaccount(const Array& params, bool fHelp)
+void GetAccountPubKeys(string strAccount, set<CScript>& setPubKey)
 {
-    if (fHelp || params.size() < 1 || params.size() > 2)
-        throw runtime_error(
-            "getreceivedbyaccount <account> [minconf=1]\n"
-            "Returns the total amount received by addresses with <account> in transactions with at least [minconf] confirmations.");
-
-    // Minimum confirmations
-    int nMinDepth = 1;
-    if (params.size() > 1)
-        nMinDepth = params[1].get_int();
-
-    // Get the set of pub keys that have the label
-    string strAccount = params[0].get_str();
-    set<CScript> setPubKey;
     CRITICAL_BLOCK(cs_mapAddressBook)
     {
         foreach(const PAIRTYPE(string, string)& item, mapAddressBook)
@@ -528,6 +506,25 @@ Value getreceivedbyaccount(const Array& params, bool fHelp)
             }
         }
     }
+}
+
+
+Value getreceivedbyaccount(const Array& params, bool fHelp)
+{
+    if (fHelp || params.size() < 1 || params.size() > 2)
+        throw runtime_error(
+            "getreceivedbyaccount <account> [minconf=1]\n"
+            "Returns the total amount received by addresses with <account> in transactions with at least [minconf] confirmations.");
+
+    // Minimum confirmations
+    int nMinDepth = 1;
+    if (params.size() > 1)
+        nMinDepth = params[1].get_int();
+
+    // Get the set of pub keys that have the label
+    string strAccount = params[0].get_str();
+    set<CScript> setPubKey;
+    GetAccountPubKeys(strAccount, setPubKey);
 
     // Tally
     int64 nAmount = 0;
@@ -552,24 +549,8 @@ Value getreceivedbyaccount(const Array& params, bool fHelp)
 
 int64 GetAccountBalance(CWalletDB& walletdb, const string& strAccount, int nMinDepth)
 {
-    // Get the set of pub keys that have the account
     set<CScript> setPubKey;
-    CRITICAL_BLOCK(cs_mapAddressBook)
-    {
-        foreach(const PAIRTYPE(string, string)& item, mapAddressBook)
-        {
-            const string& strAddress = item.first;
-            const string& strName = item.second;
-            if (strName == strAccount)
-            {
-                // We're only counting our own valid bitcoin addresses and not ip addresses
-                CScript scriptPubKey;
-                if (scriptPubKey.SetBitcoinAddress(strAddress))
-                    if (IsMine(scriptPubKey))
-                        setPubKey.insert(scriptPubKey);
-            }
-        }
-    }
+    GetAccountPubKeys(strAccount, setPubKey);
 
     int64 nBalance = 0;
     CRITICAL_BLOCK(cs_mapWallet)
@@ -581,26 +562,12 @@ int64 GetAccountBalance(CWalletDB& walletdb, const string& strAccount, int nMinD
             if (!wtx.IsFinal())
                 continue;
 
-            // Count generated blocks to account ""
-            if (wtx.IsCoinBase() && strAccount == "" && wtx.GetBlocksToMaturity() == 0)
-            {
-                nBalance += wtx.GetCredit();
-                continue;
-            }
+            int64 nGenerated, nReceived, nSent, nFee;
+            wtx.GetAccountAmounts(strAccount, setPubKey, nGenerated, nReceived, nSent, nFee);
 
-            // Tally received
-            foreach(const CTxOut& txout, wtx.vout)
-                if (setPubKey.count(txout.scriptPubKey))
-                    if (wtx.GetDepthInMainChain() >= nMinDepth)
-                        nBalance += txout.nValue;
-
-            // Tally sent
-            if (wtx.strFromAccount == strAccount)
-            {
-                int64 nNet = wtx.GetCredit() - wtx.GetDebit();
-                if (nNet < 0)
-                    nBalance += nNet;
-            }
+            if (nReceived != 0 && wtx.GetDepthInMainChain() >= nMinDepth)
+                nBalance += nReceived;
+            nBalance += nGenerated - nSent - nFee;
         }
 
         // Tally internal accounting entries
@@ -628,8 +595,6 @@ Value getbalance(const Array& params, bool fHelp)
     if (params.size() == 0)
         return ((double)GetBalance() / (double)COIN);
 
-    throw runtime_error("under construction"); //// to be released soon
-
     string strAccount = params[0].get_str();
     int nMinDepth = 1;
     if (params.size() > 1)
@@ -648,8 +613,6 @@ Value movecmd(const Array& params, bool fHelp)
             "move <fromaccount> <toaccount> <amount> [minconf=1] [comment]\n"
             "Move from one account in your wallet to another.");
 
-    throw runtime_error("under construction");
-
     string strFrom = params[0].get_str();
     string strTo = params[1].get_str();
     int64 nAmount = AmountFromValue(params[2]);
@@ -711,8 +674,6 @@ Value sendfrom(const Array& params, bool fHelp)
             "sendfrom <fromaccount> <tobitcoinaddress> <amount> [minconf=1] [comment] [comment-to]\n"
             "<amount> is a real and is rounded to the nearest 0.01");
 
-    throw runtime_error("under construction");
-
     string strAccount = params[0].get_str();
     string strAddress = params[1].get_str();
     int64 nAmount = AmountFromValue(params[2]);
@@ -888,6 +849,131 @@ Value listreceivedbyaccount(const Array& params, bool fHelp)
     return ListReceived(params, true);
 }
 
+void ListAccountTransactions(CWalletDB& walletdb, const string& strAccount, int nMinDepth, multimap<int64, Object>& ret)
+{
+    set<CScript> setPubKey;
+    GetAccountPubKeys(strAccount, setPubKey);
+
+    CRITICAL_BLOCK(cs_mapWallet)
+    {
+        // 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);
+
+            // 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)
+            {
+                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));
+            }
+        }
+
+        // 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));
+        }
+    }
+}
+
+Value listtransactions(const Array& params, bool fHelp)
+{
+    if (fHelp || params.size() < 1 || params.size() > 2)
+        throw runtime_error(
+            "listtransactions <account> [count=10]\n"
+            "Returns up to [count] most recent transactions for account <account>.");
+
+    string strAccount = params[0].get_str();
+    int nCount = 10;
+    if (params.size() > 1)
+        nCount = params[1].get_int();
+
+    CWalletDB walletdb;
+    multimap<int64, Object> mapByTime;  // keys are transaction time
+    ListAccountTransactions(walletdb, strAccount, 0, mapByTime);
+
+    // Return only last nCount items:
+    int nToErase = mapByTime.size()-nCount;
+    if (nToErase > 0)
+    {
+        multimap<int64, Object>::iterator end = mapByTime.begin();
+        std::advance(end, nToErase);
+        mapByTime.erase(mapByTime.begin(), end);
+    }
+
+    Array ret;
+    foreach(const PAIRTYPE(int64, Object)& item, mapByTime)
+        ret.push_back(item.second);
+    return ret;
+}
+
+Value gettransaction(const Array& params, bool fHelp)
+{
+    if (fHelp || params.size() != 1)
+        throw runtime_error(
+            "gettransaction <txid>\n"
+            "Get detailed information about <txid>");
+
+    uint256 hash;
+    hash.SetHex(params[0].get_str());
+
+    Object entry;
+    CRITICAL_BLOCK(cs_mapWallet)
+    {
+        if (!mapWallet.count(hash))
+            throw JSONRPCError(-5, "Invalid transaction id");
+        const CWalletTx& wtx = mapWallet[hash];
+
+        int64 nCredit = wtx.GetCredit();
+        int64 nDebit = wtx.GetDebit();
+        int64 nNet = nCredit - nDebit;
+        int64 nFee = (wtx.IsFromMe() ? wtx.GetValueOut() - nDebit : 0);
+
+        entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee)));
+        if (wtx.IsFromMe())
+            entry.push_back(Pair("fee", ValueFromAmount(nFee)));
+        WalletTxToJSON(mapWallet[hash], entry);
+    }
+
+    return entry;
+}
+
 
 Value backupwallet(const Array& params, bool fHelp)
 {
@@ -1086,6 +1172,8 @@ pair<string, rpcfn_type> pCallTable[] =
     make_pair("getbalance",            &getbalance),
     make_pair("move",                  &movecmd),
     make_pair("sendfrom",              &sendfrom),
+    make_pair("gettransaction",        &gettransaction),
+    make_pair("listtransactions",      &listtransactions),
     make_pair("getwork",               &getwork),
 };
 map<string, rpcfn_type> mapCallTable(pCallTable, pCallTable + sizeof(pCallTable)/sizeof(pCallTable[0]));
@@ -1706,8 +1794,6 @@ int CommandLineRPC(int argc, char *argv[])
         if (strMethod == "setgenerate"            && n > 0) ConvertTo<bool>(params[0]);
         if (strMethod == "setgenerate"            && n > 1) ConvertTo<boost::int64_t>(params[1]);
         if (strMethod == "sendtoaddress"          && n > 1) ConvertTo<double>(params[1]);
-        if (strMethod == "listtransactions"       && n > 0) ConvertTo<boost::int64_t>(params[0]);
-        if (strMethod == "listtransactions"       && n > 1) ConvertTo<bool>(params[1]);
         if (strMethod == "getamountreceived"      && n > 1) ConvertTo<boost::int64_t>(params[1]); // deprecated
         if (strMethod == "getreceivedbyaddress"   && n > 1) ConvertTo<boost::int64_t>(params[1]);
         if (strMethod == "getreceivedbyaccount"   && n > 1) ConvertTo<boost::int64_t>(params[1]);
@@ -1725,6 +1811,7 @@ int CommandLineRPC(int argc, char *argv[])
         if (strMethod == "move"                   && n > 3) ConvertTo<boost::int64_t>(params[3]);
         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]);
 
         // Execute
         Object reply = CallRPC(strMethod, params);
diff --git a/ui.cpp b/ui.cpp
index 321214f..213cf76 100644 (file)
--- a/ui.cpp
+++ b/ui.cpp
@@ -697,16 +697,13 @@ bool CMainFrame::InsertTransaction(const CWalletTx& wtx, bool fNew, int nIndex)
         if (fAllFromMe && fAllToMe)
         {
             // Payment to self
-            int64 nValue = wtx.vout[0].nValue;
+            int64 nChange = wtx.GetChange();
             InsertLine(fNew, nIndex, hash, strSort, colour,
                        strStatus,
                        nTime ? DateTimeStr(nTime) : "",
                        _("Payment to yourself"),
-                       "",
-                       "");
-            /// issue: can't tell which is the payment and which is the change anymore
-            //           FormatMoney(nNet - nValue, true),
-            //           FormatMoney(nValue, true));
+                       FormatMoney(-(nDebit - nChange), true),
+                       FormatMoney(nCredit - nChange, true));
         }
         else if (fAllFromMe)
         {
@@ -1376,10 +1373,10 @@ CTxDetailsDialog::CTxDetailsDialog(wxWindow* parent, CWalletTx wtx) : CTxDetails
                 if (fAllToMe)
                 {
                     // Payment to self
-                    /// issue: can't tell which is the payment and which is the change anymore
-                    //int64 nValue = wtx.vout[0].nValue;
-                    //strHTML += _("<b>Debit:</b> ") + FormatMoney(-nValue) + "<br>";
-                    //strHTML += _("<b>Credit:</b> ") + FormatMoney(nValue) + "<br>";
+                    int64 nChange = wtx.GetChange();
+                    int64 nValue = nCredit - nChange;
+                    strHTML += _("<b>Debit:</b> ") + FormatMoney(-nValue) + "<br>";
+                    strHTML += _("<b>Credit:</b> ") + FormatMoney(nValue) + "<br>";
                 }
 
                 int64 nTxFee = nDebit - wtx.GetValueOut();