Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPC: Scanforunspent #1547

Merged
merged 5 commits into from
Oct 5, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/rpcclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
245 changes: 244 additions & 1 deletion src/rpcrawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 <address> <block-start> <block-end> [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"
"<address> --------> Multi-signature address\n"
"<block-start> ----> Block number to start search from\n"
"<block-end> ------> Block number to end search on\n"
"\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)");

// 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<int64_t, std::pair<uint256, unsigned int>> uMultisig;

LOCK(cs_main);
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move the LOCK into the nested scope?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes i'll move in inward when i get back home


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 << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<id>\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 << "<tx" << nCount << ">\n";
Copy link
Member

@cyrossignol cyrossignol Oct 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to use XML for a machine-readable format, we may want to move nCount into a child element:

<tx>
  <num>1</num>
  ...
</tx>

...or an attribute:

<tx num="1">...</tx>

...or remove it altogether instead of appending it to the tag name. This prevents some XML parsers from logically grouping the <tx> items into a list structure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

originally i wanted by but can't have numbers in main part of tag. i'll play around with this idea

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

num="1" doesnt do anything really but cleaner then <tx#> and probably better supported for xml parsers

exportoutput << spacing << spacing << "<txid>" << data.second.first.ToString() << "</txid>\n";
exportoutput << spacing << spacing << "<vout>" << data.second.second << "</vout>\n";
exportoutput << spacing << spacing << "<value>" << std::fixed << setprecision(8) << data.first / (double)COIN << "</value>\n";
exportoutput << spacing << "</tx" << nCount << ">\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 << "</id>\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/rpc" / 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)
Expand Down
1 change: 1 addition & 0 deletions src/rpcserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
1 change: 1 addition & 0 deletions src/rpcserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down