From 34aa666dc11a8450f289ecbf32a7356f36f3bdd6 Mon Sep 17 00:00:00 2001 From: Paul Jensen Date: Thu, 3 Oct 2019 10:04:32 -0700 Subject: [PATCH 1/5] scanforunspent rpc --- src/rpcclient.cpp | 3 + src/rpcrawtransaction.cpp | 245 +++++++++++++++++++++++++++++++++++++- src/rpcserver.cpp | 1 + src/rpcserver.h | 1 + 4 files changed, 249 insertions(+), 1 deletion(-) diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 868e2a3651..582c8b0ed2 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -145,6 +145,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "rainbymagnitude" , 0 }, { "reservebalance" , 0 }, { "reservebalance" , 1 }, + { "scanforunspent" , 1 }, + { "scanforunspent" , 2 }, + { "scanforunspent" , 3 }, { "sendfrom" , 2 }, { "sendfrom" , 3 }, { "sendmany" , 1 }, diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 101c833f4d..88f393efec 100755 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -768,7 +768,7 @@ UniValue consolidateunspent(const UniValue& params, bool fHelp) return result; } -// MultiSig Tool +// MultiSig Tools UniValue consolidatemsunspent(const UniValue& params, bool fHelp) { if (fHelp || params.size() < 5) @@ -1080,6 +1080,249 @@ UniValue consolidatemsunspent(const UniValue& params, bool fHelp) return result; } +UniValue scanforunspent(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() < 3 || params.size() > 5 || params.size() == 4) + throw runtime_error( + "scanforunspent
[bool:export] [export-type]\n" + "\n" + "Searches a block range for a specified address with unspent utxos\n" + "and displays them in a json response with the option of exporting\n" + "to file\n" + "\n" + "Parameters required:\n" + "
--------> Multi-signature address\n" + " ----> Block number to start search from\n" + " ------> Block number to end search on\n" + "\n" + "Optional:\n" + "[export] ---------> Exports to a file in backup-dir in format of multisigaddress-datetime.type\n" + "[type] -----------> Export to a file with file type (xml or txt -- Required if export true)"); + + // Parameters + bool fExport = false; + + std::string sAddress = params[0].get_str(); + int nBlockStart = params[1].get_int(); + int nBlockEnd = params[2].get_int(); + int nType = 0; + + if (params.size() > 3) + { + fExport = params[3].get_bool(); + + if (params[4].get_str() == "xml") + nType = 0; + + else if (params[4].get_str() == "txt") + nType = 1; + + else + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid export type"); + } + + // Parameter Sanity Check + if (nBlockStart < 1 || nBlockStart > nBestHeight || nBlockStart > nBlockEnd) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block-start"); + + if (nBlockEnd < 1 || nBlockEnd > nBestHeight || nBlockEnd <= nBlockStart) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block-end"); + + CBitcoinAddress Address(sAddress); + + if (!Address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Gridcoin Address"); + + std::unordered_multimap> uMultisig; + + LOCK(cs_main); + { + + BlockFinder blockfinder; + + CBlockIndex* pblkindex = blockfinder.FindByHeight((nBlockStart - 1)); + + if (!pblkindex) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + + while (pblkindex->nHeight < nBlockEnd) + { + pblkindex = pblkindex->pnext; + + CBlock block; + + if (!block.ReadFromDisk(pblkindex, true)) + throw JSONRPCError(RPC_PARSE_ERROR, "Unable to read block from disk!"); + + // No Transactions in block outside of block creation + if (block.vtx.size() < 3) + continue; + + for (unsigned int i = 2; i < block.vtx.size(); i++) + { + // Load Transaction + CTransaction tx; + CTxDB txdb("r"); + CTxIndex txindex; + uint256 hash; + + hash = block.vtx[i].GetHash(); + + // Incase a fail here we can just continue thou it shouldn't happen + if (!tx.ReadFromDisk(txdb, COutPoint(hash, 0), txindex)) + continue; + + // Extract the address from the transaction + for (unsigned int j = 0; j < tx.vout.size(); j++) + { + const CTxOut& txout = tx.vout[j]; + CTxDestination txaddress; + + // Pass failures here thou we shouldn't have any failures + if (!ExtractDestination(txout.scriptPubKey, txaddress)) + continue; + + // If we found a match to multisig address do our work + if (CBitcoinAddress(txaddress) == Address) + { + // Check if this output is alread spent + COutPoint dummy = COutPoint(tx.GetHash(), j); + + // This is spent so move along + if (!txindex.vSpent[dummy.n].IsNull()) + continue; + + // Add to multimap + uMultisig.insert(std::make_pair(txout.nValue, std::make_pair(tx.GetHash(), j))); + } + } + } + } + } + + UniValue result(UniValue::VARR); + UniValue res(UniValue::VOBJ); + UniValue txres(UniValue::VARR); + + res.pushKV("Block Start", nBlockStart); + res.pushKV("Block End", nBlockEnd); + // Check the end results + if (uMultisig.empty()) + res.pushKV("Result", "No utxos found in specified range"); + + else + { + std::stringstream exportoutput; + std::string spacing = " "; + + if (fExport) + { + if (nType == 0) + exportoutput << "\n\n"; + + else + exportoutput << "TXID / VOUT / Value\n"; + } + + int nCount = 0; + int64_t nValue = 0; + + // Process the map + for (const auto& data : uMultisig) + { + nCount++; + + nValue += data.first; + + UniValue txdata(UniValue::VOBJ); + + txdata.pushKV("txid", data.second.first.ToString()); + txdata.pushKV("vout", (int)data.second.second); + txdata.pushKV("value", ValueFromAmount(data.first)); + + txres.push_back(txdata); + // Parse into type file here + + if (fExport) + { + if (nType == 0) + { + exportoutput << spacing << "\n"; + exportoutput << spacing << spacing << "" << data.second.first.ToString() << "\n"; + exportoutput << spacing << spacing << "" << data.second.second << "\n"; + exportoutput << spacing << spacing << "" << std::fixed << setprecision(8) << data.first / (double)COIN << "\n"; + exportoutput << spacing << "\n"; + } + + else + exportoutput << data.second.first.ToString() << " / " << data.second.second << " / " << std::fixed << setprecision(8) << data.first / (double)COIN << "\n"; + } + } + + res.pushKV("Block Start", nBlockStart); + res.pushKV("Block End", nBlockEnd); + res.pushKV("Total UTXO Count", nCount); + res.pushKV("Total Value", ValueFromAmount(nValue)); + + if (fExport) + { + // Complete xml file if its xml + if (nType == 0) + exportoutput << "\n"; + + std::ofstream dataout; + + // We will place this in wallet backups as a safer location then in main data directory + boost::filesystem::path exportpath; + + time_t biTime; + struct tm * blTime; + time (&biTime); + blTime = localtime(&biTime); + char boTime[200]; + strftime(boTime, sizeof(boTime), "%Y-%m-%dT%H-%M-%S", blTime); + + std::string exportfile = params[0].get_str() + "-" + std::string(boTime) + "." + params[4].get_str(); + + std::string backupdir = GetArg("-backupdir", ""); + + if (backupdir.empty()) + exportpath = GetDataDir() / "walletbackups" / exportfile; + + else + exportpath = backupdir + "/" + exportfile; + + boost::filesystem::create_directory(exportpath.parent_path()); + + dataout.open(exportpath.string().c_str()); + + if (!dataout) + { + res.pushKV("Export failed", "Failed to open stream for export file"); + + fExport = false; + } + + else + { + const std::string& out = exportoutput.str(); + + dataout << out; + + dataout.close(); + } + + } + } + + if (!txres.empty()) + result.push_back(txres); + + result.push_back(res); + + return result; +} + UniValue createrawtransaction(const UniValue& params, bool fHelp) { if (fHelp || params.size() != 2) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index e35af5e8a8..6da11f5d34 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -321,6 +321,7 @@ static const CRPCCommand vRPCCommands[] = { "repairwallet", &repairwallet, cat_wallet }, { "resendtx", &resendtx, cat_wallet }, { "reservebalance", &reservebalance, cat_wallet }, + { "scanforunspent", &scanforunspent, cat_wallet }, { "sendfrom", &sendfrom, cat_wallet }, { "sendmany", &sendmany, cat_wallet }, { "sendrawtransaction", &sendrawtransaction, cat_wallet }, diff --git a/src/rpcserver.h b/src/rpcserver.h index 33b0a3c329..04d96f7db6 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -142,6 +142,7 @@ extern UniValue rainbymagnitude(const UniValue& params, bool fHelp); extern UniValue repairwallet(const UniValue& params, bool fHelp); extern UniValue resendtx(const UniValue& params, bool fHelp); extern UniValue reservebalance(const UniValue& params, bool fHelp); +extern UniValue scanforunspent(const UniValue& params, bool fHelp); extern UniValue sendfrom(const UniValue& params, bool fHelp); extern UniValue sendmany(const UniValue& params, bool fHelp); extern UniValue sendrawtransaction(const UniValue& params, bool fHelp); From 012bdb64350bb50157435b7e7cee3e34c0545c99 Mon Sep 17 00:00:00 2001 From: Paul Jensen Date: Thu, 3 Oct 2019 10:15:12 -0700 Subject: [PATCH 2/5] spacing fix --- src/rpcclient.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 582c8b0ed2..845ff49573 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -145,9 +145,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "rainbymagnitude" , 0 }, { "reservebalance" , 0 }, { "reservebalance" , 1 }, - { "scanforunspent" , 1 }, - { "scanforunspent" , 2 }, - { "scanforunspent" , 3 }, + { "scanforunspent" , 1 }, + { "scanforunspent" , 2 }, + { "scanforunspent" , 3 }, { "sendfrom" , 2 }, { "sendfrom" , 3 }, { "sendmany" , 1 }, From 689342344d4ce6a6d8399dd4431914e3eaa7747f Mon Sep 17 00:00:00 2001 From: Paul Jensen Date: Thu, 3 Oct 2019 14:07:59 -0700 Subject: [PATCH 3/5] store in rpc subfolder of walletbackups --- src/rpcrawtransaction.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 88f393efec..ca18f70ce9 100755 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -1096,7 +1096,7 @@ UniValue scanforunspent(const UniValue& params, bool fHelp) " ------> Block number to end search on\n" "\n" "Optional:\n" - "[export] ---------> Exports to a file in backup-dir in format of multisigaddress-datetime.type\n" + "[export] ---------> Exports to a file in backup-dir/rpc in format of multisigaddress-datetime.type\n" "[type] -----------> Export to a file with file type (xml or txt -- Required if export true)"); // Parameters @@ -1287,7 +1287,7 @@ UniValue scanforunspent(const UniValue& params, bool fHelp) std::string backupdir = GetArg("-backupdir", ""); if (backupdir.empty()) - exportpath = GetDataDir() / "walletbackups" / exportfile; + exportpath = GetDataDir() / "walletbackups/rpc" / exportfile; else exportpath = backupdir + "/" + exportfile; From 5dd666c304bc19af46ad61ab76983eb4ba153e1b Mon Sep 17 00:00:00 2001 From: Paul Jensen Date: Thu, 3 Oct 2019 21:24:10 -0700 Subject: [PATCH 4/5] Add json support for export. push txres to dataout at end to avoid unexpected white space, and add id tag instead to xml --- src/rpcrawtransaction.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index ca18f70ce9..46a1a20cf4 100755 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -1097,7 +1097,7 @@ UniValue scanforunspent(const UniValue& params, bool fHelp) "\n" "Optional:\n" "[export] ---------> Exports to a file in backup-dir/rpc in format of multisigaddress-datetime.type\n" - "[type] -----------> Export to a file with file type (xml or txt -- Required if export true)"); + "[type] -----------> Export to a file with file type (xml, txt or json -- Required if export true)"); // Parameters bool fExport = false; @@ -1117,6 +1117,9 @@ UniValue scanforunspent(const UniValue& params, bool fHelp) else if (params[4].get_str() == "txt") nType = 1; + else if (params[4].get_str() == "json") + nType = 2; + else throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid export type"); } @@ -1220,8 +1223,9 @@ UniValue scanforunspent(const UniValue& params, bool fHelp) if (nType == 0) exportoutput << "\n\n"; - else + else if (nType == 1) exportoutput << "TXID / VOUT / Value\n"; + } int nCount = 0; @@ -1247,14 +1251,14 @@ UniValue scanforunspent(const UniValue& params, bool fHelp) { if (nType == 0) { - exportoutput << spacing << "\n"; + exportoutput << spacing << "\n"; exportoutput << spacing << spacing << "" << data.second.first.ToString() << "\n"; exportoutput << spacing << spacing << "" << data.second.second << "\n"; exportoutput << spacing << spacing << "" << std::fixed << setprecision(8) << data.first / (double)COIN << "\n"; - exportoutput << spacing << "\n"; + exportoutput << spacing << "\n"; } - else + else if (nType == 1) exportoutput << data.second.first.ToString() << " / " << data.second.second << " / " << std::fixed << setprecision(8) << data.first / (double)COIN << "\n"; } } @@ -1305,9 +1309,15 @@ UniValue scanforunspent(const UniValue& params, bool fHelp) else { - const std::string& out = exportoutput.str(); + if (nType == 0 || nType == 1) + { + const std::string& out = exportoutput.str(); + + dataout << out; + } - dataout << out; + else + dataout << txres.write(2); dataout.close(); } From 44181b53f42dda4cec02ceeb505fde0d46bd5324 Mon Sep 17 00:00:00 2001 From: Paul Jensen Date: Fri, 4 Oct 2019 21:12:25 -0700 Subject: [PATCH 5/5] nest the lock for cs_main to limit scope --- src/rpcrawtransaction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 46a1a20cf4..6bc8857000 100755 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -1138,8 +1138,8 @@ UniValue scanforunspent(const UniValue& params, bool fHelp) std::unordered_multimap> uMultisig; - LOCK(cs_main); { + LOCK(cs_main); BlockFinder blockfinder;