Index: rpc.cpp =================================================================== --- rpc.cpp (revision 119) +++ rpc.cpp (working copy) @@ -342,25 +342,207 @@ return "sent"; } +enum txn_classification +{ + txn_unknown, + txn_generated, + txn_credit, + txn_debit, + txn_mixed_debit, +}; +static const char *txn_class_str[] = { + "unknown", + "generated", + "credit", + "debit", + "mixed_debit", +}; + +struct txnitem +{ + uint160 hash160; + int64 nAmount; + int nConf; + enum txn_classification txnClass; + txnitem() + { + hash160 = 0; + nAmount = 0; + nConf = INT_MAX; + txnClass = txn_unknown; + } +}; + +bool txnitem_cmp(const txnitem& a, const txnitem &b) +{ + return a.nConf < b.nConf; +} + +Value ListTransactions(int64 nCount, int64 nMinDepth, bool fGenerated) +{ + vector tv; + CRITICAL_BLOCK(cs_mapWallet) + { + for (map::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx& wtx = (*it).second; + int64 nCredit = wtx.GetCredit(true); + int64 nDebit = wtx.GetDebit(); + int64 nNet = nCredit - nDebit; + + bool gen = wtx.IsCoinBase(); + if (gen) { + if (!fGenerated) + continue; + if (!wtx.IsInMainChain()) + continue; + } + + int nDepth = wtx.GetDepthInMainChain(); + if (nDepth < nMinDepth) + continue; + + if (nNet > 0) + { + foreach(const CTxOut& txout, wtx.vout) + { + uint160 hash160 = txout.scriptPubKey.GetBitcoinAddressHash160(); + + if (!txout.IsMine()) + continue; + + txnitem item; + item.hash160 = hash160; + item.nAmount = txout.nValue; + item.nConf = min(item.nConf, nDepth); + if (gen) + item.txnClass = txn_generated; + else + item.txnClass = txn_credit; + + tv.push_back(item); + } + } + else + { + bool fAllFromMe = true; + foreach(const CTxIn& txin, wtx.vin) + fAllFromMe = fAllFromMe && txin.IsMine(); + + bool fAllToMe = true; + foreach(const CTxOut& txout, wtx.vout) + fAllToMe = fAllToMe && txout.IsMine(); + + if (fAllFromMe && fAllToMe) // payment to self + { + txnitem item; + item.hash160 = wtx.vout[0].scriptPubKey.GetBitcoinAddressHash160(); + item.nAmount = wtx.vout[0].nValue; + item.nConf = min(item.nConf, nDepth); + item.txnClass = txn_credit; // take your pick, cred or deb + + tv.push_back(item); + } + else if (fAllFromMe) // debit txn + { + int64 nTxFee = nDebit - wtx.GetValueOut(); + for (int nOut = 0; nOut < wtx.vout.size(); nOut++) + { + const CTxOut& txout = wtx.vout[nOut]; + if (txout.IsMine()) + continue; + + uint160 hash160; + ExtractHash160(txout.scriptPubKey, hash160); + + int64 nValue = txout.nValue; + if (nTxFee > 0) + { + nValue += nTxFee; + nTxFee = 0; + } + + txnitem item; + item.hash160 = hash160; + item.nAmount = nValue; + item.nConf = min(item.nConf, nDepth); + item.txnClass = txn_debit; + + tv.push_back(item); + } + } + else // mixed debit txn + { // can't separate payees + txnitem item; + item.nAmount = nNet; + item.nConf = min(item.nConf, nDepth); + item.txnClass = txn_mixed_debit; + + tv.push_back(item); + } + } + } + } + + std::sort(tv.begin(), tv.end(), txnitem_cmp); + + // Reply + Array ret; + int64 returned = 0; + CRITICAL_BLOCK(cs_mapAddressBook) + { + foreach(const txnitem& txn, tv) + { + if ((nCount > 0) && (returned >= nCount)) + break; + + string strAddress = Hash160ToAddress(txn.hash160); + string strLabel, strClass; + int64 nAmount = txn.nAmount; + int nConf = txn.nConf; + + map::iterator mi = mapAddressBook.find(strAddress); + if (mi != mapAddressBook.end()) + strLabel = (*mi).second; + + strClass = txn_class_str[txn.txnClass]; + + Object obj; + obj.push_back(Pair("address", strAddress)); + obj.push_back(Pair("label", strLabel)); + obj.push_back(Pair("class", strClass)); + obj.push_back(Pair("amount", (double)nAmount /(double)COIN)); + obj.push_back(Pair("confirmations", (nConf == INT_MAX ? 0 :nConf))); + ret.push_back(obj); + + returned++; + } + } + + return ret; +} + Value listtransactions(const Array& params, bool fHelp) { - if (fHelp || params.size() > 2) + if (fHelp || params.size() > 3) throw runtime_error( - "listtransactions [count=10] [includegenerated=false]\n" + "listtransactions [count=10] [minconf=1] [includegenerated=true]\n" "Returns up to [count] most recent transactions."); int64 nCount = 10; if (params.size() > 0) nCount = params[0].get_int64(); - bool fGenerated = false; + + int64 nMinDepth = 1; if (params.size() > 1) - fGenerated = params[1].get_bool(); + nMinDepth = params[1].get_int64(); - Array ret; - //// not finished - ret.push_back("not implemented yet"); - return ret; + bool fGenerated = true; + if (params.size() > 2) + fGenerated = params[2].get_bool(); + + return ListTransactions(nCount, nMinDepth, fGenerated); } @@ -638,6 +820,7 @@ make_pair("getreceivedbylabel", &getreceivedbylabel), make_pair("listreceivedbyaddress", &listreceivedbyaddress), make_pair("listreceivedbylabel", &listreceivedbylabel), + make_pair("listtransactions", &listtransactions), }; map mapCallTable(pCallTable, pCallTable + sizeof(pCallTable)/sizeof(pCallTable[0])); @@ -1092,7 +1275,8 @@ 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 == "listtransactions" && n > 1) ConvertTo(params[1]); + if (strMethod == "listtransactions" && n > 2) ConvertTo(params[2]); if (strMethod == "getamountreceived" && n > 1) ConvertTo(params[1]); // deprecated if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo(params[1]); if (strMethod == "getreceivedbylabel" && n > 1) ConvertTo(params[1]);