From bfd471f53e14c4218ae7a1544beb7f1de3e695b2 Mon Sep 17 00:00:00 2001 From: gavinandresen Date: Tue, 30 Nov 2010 18:58:11 +0000 Subject: [PATCH] JSON methods: listtransactions, gettransaction, move, sendfrom and getbalance git-svn-id: https://bitcoin.svn.sourceforge.net/svnroot/bitcoin/trunk@193 1a98c847-1fd6-4fd8-948a-caf3550aa51b --- db.cpp | 19 ++++- db.h | 1 + main.cpp | 2 +- main.h | 82 ++++++++++++++++++- rpc.cpp | 245 +++++++++++++++++++++++++++++++++++++------------------ ui.cpp | 17 ++-- 6 files changed, 268 insertions(+), 98 deletions(-) diff --git a/db.cpp b/db.cpp index 2833a8be9..40998fb74 100644 --- a/db.cpp +++ b/db.cpp @@ -596,12 +596,24 @@ bool CWalletDB::WriteAccountingEntry(const string& strAccount, const CAccounting } int64 CWalletDB::GetAccountCreditDebit(const string& strAccount) +{ + list entries; + ListAccountCreditDebit(strAccount, entries); + + int64 nCreditDebit = 0; + foreach (const CAccountingEntry& entry, entries) + nCreditDebit += entry.nCreditDebit; + + return nCreditDebit; +} + +void CWalletDB::ListAccountCreditDebit(const string& strAccount, list& 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 0f705a863..e3ffc40c6 100644 --- 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& acentries); bool LoadWallet(); protected: diff --git a/main.cpp b/main.cpp index acfcbc90f..a1865a467 100644 --- 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 e5000d757..2c24eba2d 100644 --- 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 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& 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 a7066ab3e..0d00f4cc8 100644 --- 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& setPubKey) { - if (fHelp || params.size() < 1 || params.size() > 2) - throw runtime_error( - "getreceivedbyaccount [minconf=1]\n" - "Returns the total amount received by addresses with 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 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 [minconf=1]\n" + "Returns the total amount received by addresses with 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 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 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 [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 [minconf=1] [comment] [comment-to]\n" " 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& ret) +{ + set setPubKey; + GetAccountPubKeys(strAccount, setPubKey); + + CRITICAL_BLOCK(cs_mapWallet) + { + // Wallet: generate/send/receive transactions + for (map::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 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 [count=10]\n" + "Returns up to [count] most recent transactions for account ."); + + string strAccount = params[0].get_str(); + int nCount = 10; + if (params.size() > 1) + nCount = params[1].get_int(); + + CWalletDB walletdb; + multimap mapByTime; // keys are transaction time + ListAccountTransactions(walletdb, strAccount, 0, mapByTime); + + // Return only last nCount items: + int nToErase = mapByTime.size()-nCount; + if (nToErase > 0) + { + multimap::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 \n" + "Get detailed information about "); + + 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 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 mapCallTable(pCallTable, pCallTable + sizeof(pCallTable)/sizeof(pCallTable[0])); @@ -1706,8 +1794,6 @@ int CommandLineRPC(int argc, char *argv[]) if (strMethod == "setgenerate" && n > 0) ConvertTo(params[0]); if (strMethod == "setgenerate" && n > 1) ConvertTo(params[1]); if (strMethod == "sendtoaddress" && n > 1) ConvertTo(params[1]); - if (strMethod == "listtransactions" && n > 0) ConvertTo(params[0]); - if (strMethod == "listtransactions" && n > 1) ConvertTo(params[1]); if (strMethod == "getamountreceived" && n > 1) ConvertTo(params[1]); // deprecated if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo(params[1]); if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo(params[1]); @@ -1725,6 +1811,7 @@ int CommandLineRPC(int argc, char *argv[]) if (strMethod == "move" && n > 3) ConvertTo(params[3]); if (strMethod == "sendfrom" && n > 2) ConvertTo(params[2]); if (strMethod == "sendfrom" && n > 3) ConvertTo(params[3]); + if (strMethod == "listtransactions" && n > 1) ConvertTo(params[1]); // Execute Object reply = CallRPC(strMethod, params); diff --git a/ui.cpp b/ui.cpp index 321214fe5..213cf7666 100644 --- 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 += _("Debit: ") + FormatMoney(-nValue) + "
"; - //strHTML += _("Credit: ") + FormatMoney(nValue) + "
"; + int64 nChange = wtx.GetChange(); + int64 nValue = nCredit - nChange; + strHTML += _("Debit: ") + FormatMoney(-nValue) + "
"; + strHTML += _("Credit: ") + FormatMoney(nValue) + "
"; } int64 nTxFee = nDebit - wtx.GetValueOut();