From 22dfd7359863217eb8caef75084cfa8fa8e1d8fb Mon Sep 17 00:00:00 2001 From: coderrr Date: Wed, 1 Aug 2012 12:48:42 -0400 Subject: [PATCH 1/3] Add address groupings RPC from the coincontrol patches. Signed-off-by: Gregory Maxwell --- src/bitcoinrpc.cpp | 1 + src/bitcoinrpc.h | 1 + src/rpcwallet.cpp | 27 ++++++++++ src/wallet.cpp | 123 +++++++++++++++++++++++++++++++++++++++++++++ src/wallet.h | 11 ++++ 5 files changed, 163 insertions(+) diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 5537eb4815..ebde28fe04 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -231,6 +231,7 @@ static const CRPCCommand vRPCCommands[] = { "getblockhash", &getblockhash, false, false }, { "gettransaction", &gettransaction, false, false }, { "listtransactions", &listtransactions, false, false }, + { "listaddressgroupings", &listaddressgroupings, false, false }, { "signmessage", &signmessage, false, false }, { "verifymessage", &verifymessage, false, false }, { "getwork", &getwork, true, false }, diff --git a/src/bitcoinrpc.h b/src/bitcoinrpc.h index 6a1857cb44..94446c36bb 100644 --- a/src/bitcoinrpc.h +++ b/src/bitcoinrpc.h @@ -114,6 +114,7 @@ extern json_spirit::Value addmultisigaddress(const json_spirit::Array& params, b extern json_spirit::Value listreceivedbyaddress(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value listreceivedbyaccount(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value listtransactions(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value listaddressgroupings(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value listaccounts(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value listsinceblock(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value gettransaction(const json_spirit::Array& params, bool fHelp); diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index eacb5b3b1a..519e353138 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -274,6 +274,33 @@ Value sendtoaddress(const Array& params, bool fHelp) return wtx.GetHash().GetHex(); } +Value listaddressgroupings(const Array& params, bool fHelp) +{ + if (fHelp) + throw runtime_error("listaddressgroupings"); + + Array jsonGroupings; + map balances = pwalletMain->GetAddressBalances(); + BOOST_FOREACH(set grouping, pwalletMain->GetAddressGroupings()) + { + Array jsonGrouping; + BOOST_FOREACH(string address, grouping) + { + Array addressInfo; + addressInfo.push_back(address); + addressInfo.push_back(ValueFromAmount(balances[address])); + { + LOCK(pwalletMain->cs_wallet); + if (pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get()) != pwalletMain->mapAddressBook.end()) + addressInfo.push_back(pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get())->second); + } + jsonGrouping.push_back(addressInfo); + } + jsonGroupings.push_back(jsonGrouping); + } + return jsonGroupings; +} + Value signmessage(const Array& params, bool fHelp) { if (fHelp || params.size() != 2) diff --git a/src/wallet.cpp b/src/wallet.cpp index 07a5047cef..87792e50ee 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1620,6 +1620,129 @@ int64 CWallet::GetOldestKeyPoolTime() return keypool.nTime; } +std::map CWallet::GetAddressBalances() +{ + map balances; + + { + LOCK(cs_wallet); + BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet) + { + CWalletTx *pcoin = &walletEntry.second; + + if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) + continue; + + if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) + continue; + + int nDepth = pcoin->GetDepthInMainChain(); + if (nDepth < (pcoin->IsFromMe() ? 0 : 1)) + continue; + + for (int i = 0; i < pcoin->vout.size(); i++) + { + if (!IsMine(pcoin->vout[i])) + continue; + + int64 n = pcoin->IsSpent(i) ? 0 : pcoin->vout[i].nValue; + + string addr = pcoin->GetAddressOfTxOut(i); + if (!balances.count(addr)) + balances[addr] = 0; + balances[addr] += n; + } + } + } + + return balances; +} + +set< set > CWallet::GetAddressGroupings() +{ + set< set > groupings; + set grouping; + + BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet) + { + CWalletTx *pcoin = &walletEntry.second; + + if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) + continue; + + if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) + continue; + + int nDepth = pcoin->GetDepthInMainChain(); + if (nDepth < (pcoin->IsFromMe() ? 0 : 1)) + continue; + + if (pcoin->vin.size() > 0 && IsMine(pcoin->vin[0])) + { + // group all input addresses with each other + BOOST_FOREACH(CTxIn txin, pcoin->vin) + grouping.insert(mapWallet[txin.prevout.hash].GetAddressOfTxOut(txin.prevout.n)); + + // group change with input addresses + BOOST_FOREACH(CTxOut txout, pcoin->vout) + if (IsChange(txout)) + { + CWalletTx tx = mapWallet[pcoin->vin[0].prevout.hash]; + string addr = tx.GetAddressOfTxOut(pcoin->vin[0].prevout.n); + CTxDestination txoutAddr; + ExtractDestination(txout.scriptPubKey, txoutAddr); + grouping.insert(CBitcoinAddress(txoutAddr).ToString()); + } + groupings.insert(grouping); + grouping.clear(); + } + + // group lone addrs by themselves + for (int i = 0; i < pcoin->vout.size(); i++) + if (IsMine(pcoin->vout[i])) + { + grouping.insert(pcoin->GetAddressOfTxOut(i)); + groupings.insert(grouping); + grouping.clear(); + } + } + + set< set* > uniqueGroupings; // a set of pointers to groups of addresses + map< string, set* > setmap; // map addresses to the unique group containing it + BOOST_FOREACH(set grouping, groupings) + { + // make a set of all the groups hit by this new group + set< set* > hits; + map< string, set* >::iterator it; + BOOST_FOREACH(string address, grouping) + if ((it = setmap.find(address)) != setmap.end()) + hits.insert((*it).second); + + // merge all hit groups into a new single group and delete old groups + set* merged = new set(grouping); + BOOST_FOREACH(set* hit, hits) + { + merged->insert(hit->begin(), hit->end()); + uniqueGroupings.erase(hit); + delete hit; + } + uniqueGroupings.insert(merged); + + // update setmap + BOOST_FOREACH(string element, *merged) + setmap[element] = merged; + } + + set< set > ret; + BOOST_FOREACH(set* uniqueGrouping, uniqueGroupings) + { + ret.insert(*uniqueGrouping); + delete uniqueGrouping; + } + + return ret; +} + CPubKey CReserveKey::GetReservedKey() { if (nIndex == -1) diff --git a/src/wallet.h b/src/wallet.h index 9103aa675e..5f6a6a4444 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -10,6 +10,7 @@ #include +#include "base58.h" #include "main.h" #include "key.h" #include "keystore.h" @@ -176,6 +177,9 @@ public: int64 GetOldestKeyPoolTime(); void GetAllReserveKeys(std::set& setAddress); + std::set< std::set > GetAddressGroupings(); + std::map GetAddressBalances(); + bool IsMine(const CTxIn& txin) const; int64 GetDebit(const CTxIn& txin) const; bool IsMine(const CTxOut& txout) const @@ -643,6 +647,13 @@ public: return true; } + std::string GetAddressOfTxOut(int n) + { + CTxDestination addr; + ExtractDestination(vout[n].scriptPubKey, addr); + return CBitcoinAddress(addr).ToString(); + } + bool WriteToDisk(); int64 GetTxTime() const; From 92735bca313768dbc49789566c47e3a68ecef59a Mon Sep 17 00:00:00 2001 From: Gregory Maxwell Date: Mon, 6 Aug 2012 13:02:48 -0400 Subject: [PATCH 2/3] Add txout address filtering to listunspent. This applies on top of the coincontrol listaddressgroupings patch and makes finding eligible outputs from the groups returned by listaddressgroupings possible. --- src/bitcoinrpc.cpp | 1 + src/rpcrawtransaction.cpp | 27 +++++++++++++++++++++++---- src/wallet.h | 2 +- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index ebde28fe04..c144f5fbad 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -1140,6 +1140,7 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector 1) ConvertTo(params[1]); if (strMethod == "listunspent" && n > 0) ConvertTo(params[0]); if (strMethod == "listunspent" && n > 1) ConvertTo(params[1]); + if (strMethod == "listunspent" && n > 2) ConvertTo(params[2]); if (strMethod == "getrawtransaction" && n > 1) ConvertTo(params[1]); if (strMethod == "createrawtransaction" && n > 0) ConvertTo(params[0]); if (strMethod == "createrawtransaction" && n > 1) ConvertTo(params[1]); diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index fefefc7d62..d6fb30cac6 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -138,24 +138,40 @@ Value getrawtransaction(const Array& params, bool fHelp) Value listunspent(const Array& params, bool fHelp) { - if (fHelp || params.size() > 2) + if (fHelp || params.size() > 3) throw runtime_error( - "listunspent [minconf=1] [maxconf=999999]\n" + "listunspent [minconf=1] [maxconf=9999999] ['addr1','addr2',...]\n" "Returns array of unspent transaction outputs\n" "with between minconf and maxconf (inclusive) confirmations.\n" + "Optionally filtered to only include txouts paid to specified addresses.\n" "Results are an array of Objects, each of which has:\n" "{txid, vout, scriptPubKey, amount, confirmations}"); - RPCTypeCheck(params, list_of(int_type)(int_type)); + RPCTypeCheck(params, list_of(int_type)(int_type)(array_type)); int nMinDepth = 1; if (params.size() > 0) nMinDepth = params[0].get_int(); - int nMaxDepth = 999999; + int nMaxDepth = 9999999; if (params.size() > 1) nMaxDepth = params[1].get_int(); + set setAddress; + if (params.size() > 2) + { + Array inputs = params[2].get_array(); + BOOST_FOREACH(Value& input, inputs) + { + CBitcoinAddress address(input.get_str()); + if (!address.IsValid()) + throw JSONRPCError(-5, string("Invalid Bitcoin address:")+input.get_str()); + if (setAddress.count(address)) + throw JSONRPCError(-8, string("Invalid parameter, duplicated address: ")+input.get_str()); + setAddress.insert(address); + } + } + Array results; vector vecOutputs; pwalletMain->AvailableCoins(vecOutputs, false); @@ -164,6 +180,9 @@ Value listunspent(const Array& params, bool fHelp) if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) continue; + if (setAddress.size() && !setAddress.count(out.tx->GetAddressOfTxOut(out.i))) + continue; + int64 nValue = out.tx->vout[out.i].nValue; const CScript& pk = out.tx->vout[out.i].scriptPubKey; Object entry; diff --git a/src/wallet.h b/src/wallet.h index 5f6a6a4444..f02c1467b5 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -647,7 +647,7 @@ public: return true; } - std::string GetAddressOfTxOut(int n) + std::string GetAddressOfTxOut(int n) const { CTxDestination addr; ExtractDestination(vout[n].scriptPubKey, addr); From b1093efa833376a7883deb0cbcddd0aed364de84 Mon Sep 17 00:00:00 2001 From: Gregory Maxwell Date: Mon, 20 Aug 2012 13:43:33 -0400 Subject: [PATCH 3/3] Change CWallet addressgrouping to use CTxDestination instead of strings. This is cleanup for the listaddressgroupings code. Also add some real help text. --- src/rpcrawtransaction.cpp | 17 +++++++--- src/rpcwallet.cpp | 14 +++++--- src/wallet.cpp | 68 +++++++++++++++++++-------------------- src/wallet.h | 12 ++----- 4 files changed, 57 insertions(+), 54 deletions(-) diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index d6fb30cac6..2b6f4f7a4b 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -140,7 +140,7 @@ Value listunspent(const Array& params, bool fHelp) { if (fHelp || params.size() > 3) throw runtime_error( - "listunspent [minconf=1] [maxconf=9999999] ['addr1','addr2',...]\n" + "listunspent [minconf=1] [maxconf=9999999] [\"address\",...]\n" "Returns array of unspent transaction outputs\n" "with between minconf and maxconf (inclusive) confirmations.\n" "Optionally filtered to only include txouts paid to specified addresses.\n" @@ -165,7 +165,7 @@ Value listunspent(const Array& params, bool fHelp) { CBitcoinAddress address(input.get_str()); if (!address.IsValid()) - throw JSONRPCError(-5, string("Invalid Bitcoin address:")+input.get_str()); + throw JSONRPCError(-5, string("Invalid Bitcoin address: ")+input.get_str()); if (setAddress.count(address)) throw JSONRPCError(-8, string("Invalid parameter, duplicated address: ")+input.get_str()); setAddress.insert(address); @@ -180,8 +180,15 @@ Value listunspent(const Array& params, bool fHelp) if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) continue; - if (setAddress.size() && !setAddress.count(out.tx->GetAddressOfTxOut(out.i))) - continue; + if(setAddress.size()) + { + CTxDestination address; + if(!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) + continue; + + if (!setAddress.count(address)) + continue; + } int64 nValue = out.tx->vout[out.i].nValue; const CScript& pk = out.tx->vout[out.i].scriptPubKey; @@ -243,7 +250,7 @@ Value createrawtransaction(const Array& params, bool fHelp) { CBitcoinAddress address(s.name_); if (!address.IsValid()) - throw JSONRPCError(-5, string("Invalid Bitcoin address:")+s.name_); + throw JSONRPCError(-5, string("Invalid Bitcoin address: ")+s.name_); if (setAddress.count(address)) throw JSONRPCError(-8, string("Invalid parameter, duplicated address: ")+s.name_); diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 519e353138..a32eb7801f 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -277,17 +277,21 @@ Value sendtoaddress(const Array& params, bool fHelp) Value listaddressgroupings(const Array& params, bool fHelp) { if (fHelp) - throw runtime_error("listaddressgroupings"); + throw runtime_error( + "listaddressgroupings\n" + "Lists groups of addresses which have had their common ownership\n" + "made public by common use as inputs or as the resulting change\n" + "in past transactions"); Array jsonGroupings; - map balances = pwalletMain->GetAddressBalances(); - BOOST_FOREACH(set grouping, pwalletMain->GetAddressGroupings()) + map balances = pwalletMain->GetAddressBalances(); + BOOST_FOREACH(set grouping, pwalletMain->GetAddressGroupings()) { Array jsonGrouping; - BOOST_FOREACH(string address, grouping) + BOOST_FOREACH(CTxDestination address, grouping) { Array addressInfo; - addressInfo.push_back(address); + addressInfo.push_back(CBitcoinAddress(address).ToString()); addressInfo.push_back(ValueFromAmount(balances[address])); { LOCK(pwalletMain->cs_wallet); diff --git a/src/wallet.cpp b/src/wallet.cpp index 87792e50ee..a5a4e7e431 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1620,9 +1620,9 @@ int64 CWallet::GetOldestKeyPoolTime() return keypool.nTime; } -std::map CWallet::GetAddressBalances() +std::map CWallet::GetAddressBalances() { - map balances; + map balances; { LOCK(cs_wallet); @@ -1640,14 +1640,16 @@ std::map CWallet::GetAddressBalances() if (nDepth < (pcoin->IsFromMe() ? 0 : 1)) continue; - for (int i = 0; i < pcoin->vout.size(); i++) + for (unsigned int i = 0; i < pcoin->vout.size(); i++) { + CTxDestination addr; if (!IsMine(pcoin->vout[i])) continue; + if(!ExtractDestination(pcoin->vout[i].scriptPubKey, addr)) + continue; int64 n = pcoin->IsSpent(i) ? 0 : pcoin->vout[i].nValue; - string addr = pcoin->GetAddressOfTxOut(i); if (!balances.count(addr)) balances[addr] = 0; balances[addr] += n; @@ -1658,69 +1660,67 @@ std::map CWallet::GetAddressBalances() return balances; } -set< set > CWallet::GetAddressGroupings() +set< set > CWallet::GetAddressGroupings() { - set< set > groupings; - set grouping; + set< set > groupings; + set grouping; BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet) { CWalletTx *pcoin = &walletEntry.second; - if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) - continue; - - if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) - continue; - - int nDepth = pcoin->GetDepthInMainChain(); - if (nDepth < (pcoin->IsFromMe() ? 0 : 1)) - continue; - if (pcoin->vin.size() > 0 && IsMine(pcoin->vin[0])) { // group all input addresses with each other BOOST_FOREACH(CTxIn txin, pcoin->vin) - grouping.insert(mapWallet[txin.prevout.hash].GetAddressOfTxOut(txin.prevout.n)); + { + CTxDestination address; + if(!ExtractDestination(mapWallet[txin.prevout.hash].vout[txin.prevout.n].scriptPubKey, address)) + continue; + grouping.insert(address); + } // group change with input addresses BOOST_FOREACH(CTxOut txout, pcoin->vout) if (IsChange(txout)) { CWalletTx tx = mapWallet[pcoin->vin[0].prevout.hash]; - string addr = tx.GetAddressOfTxOut(pcoin->vin[0].prevout.n); CTxDestination txoutAddr; - ExtractDestination(txout.scriptPubKey, txoutAddr); - grouping.insert(CBitcoinAddress(txoutAddr).ToString()); + if(!ExtractDestination(txout.scriptPubKey, txoutAddr)) + continue; + grouping.insert(txoutAddr); } groupings.insert(grouping); grouping.clear(); } // group lone addrs by themselves - for (int i = 0; i < pcoin->vout.size(); i++) + for (unsigned int i = 0; i < pcoin->vout.size(); i++) if (IsMine(pcoin->vout[i])) { - grouping.insert(pcoin->GetAddressOfTxOut(i)); + CTxDestination address; + if(!ExtractDestination(pcoin->vout[i].scriptPubKey, address)) + continue; + grouping.insert(address); groupings.insert(grouping); grouping.clear(); } } - set< set* > uniqueGroupings; // a set of pointers to groups of addresses - map< string, set* > setmap; // map addresses to the unique group containing it - BOOST_FOREACH(set grouping, groupings) + set< set* > uniqueGroupings; // a set of pointers to groups of addresses + map< CTxDestination, set* > setmap; // map addresses to the unique group containing it + BOOST_FOREACH(set grouping, groupings) { // make a set of all the groups hit by this new group - set< set* > hits; - map< string, set* >::iterator it; - BOOST_FOREACH(string address, grouping) + set< set* > hits; + map< CTxDestination, set* >::iterator it; + BOOST_FOREACH(CTxDestination address, grouping) if ((it = setmap.find(address)) != setmap.end()) hits.insert((*it).second); // merge all hit groups into a new single group and delete old groups - set* merged = new set(grouping); - BOOST_FOREACH(set* hit, hits) + set* merged = new set(grouping); + BOOST_FOREACH(set* hit, hits) { merged->insert(hit->begin(), hit->end()); uniqueGroupings.erase(hit); @@ -1729,12 +1729,12 @@ set< set > CWallet::GetAddressGroupings() uniqueGroupings.insert(merged); // update setmap - BOOST_FOREACH(string element, *merged) + BOOST_FOREACH(CTxDestination element, *merged) setmap[element] = merged; } - set< set > ret; - BOOST_FOREACH(set* uniqueGrouping, uniqueGroupings) + set< set > ret; + BOOST_FOREACH(set* uniqueGrouping, uniqueGroupings) { ret.insert(*uniqueGrouping); delete uniqueGrouping; diff --git a/src/wallet.h b/src/wallet.h index f02c1467b5..428cff7480 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -10,7 +10,6 @@ #include -#include "base58.h" #include "main.h" #include "key.h" #include "keystore.h" @@ -177,8 +176,8 @@ public: int64 GetOldestKeyPoolTime(); void GetAllReserveKeys(std::set& setAddress); - std::set< std::set > GetAddressGroupings(); - std::map GetAddressBalances(); + std::set< std::set > GetAddressGroupings(); + std::map GetAddressBalances(); bool IsMine(const CTxIn& txin) const; int64 GetDebit(const CTxIn& txin) const; @@ -647,13 +646,6 @@ public: return true; } - std::string GetAddressOfTxOut(int n) const - { - CTxDestination addr; - ExtractDestination(vout[n].scriptPubKey, addr); - return CBitcoinAddress(addr).ToString(); - } - bool WriteToDisk(); int64 GetTxTime() const;