#include "zcashdrpc.h" ZcashdRPC::ZcashdRPC() { } ZcashdRPC::~ZcashdRPC() { delete conn; } void ZcashdRPC::setConnection(Connection* c) { if (conn) { delete conn; } conn = c; } bool ZcashdRPC::haveConnection() { return conn != nullptr; } void ZcashdRPC::fetchTAddresses(const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "getaddressesbyaccount"}, {"params", {""}} }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::fetchZAddresses(const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "z_listaddresses"}, }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::fetchTransparentUnspent(const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "listunspent"}, {"params", {0}} // Get UTXOs with 0 confirmations as well. }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::fetchZUnspent(const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "z_listunspent"}, {"params", {0}} // Get UTXOs with 0 confirmations as well. }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::createNewZaddr(bool sapling, const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "z_getnewaddress"}, {"params", { sapling ? "sapling" : "sprout" }}, }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::createNewTaddr(const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "getnewaddress"}, }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::fetchZPrivKey(QString addr, const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "z_exportkey"}, {"params", { addr.toStdString() }}, }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::fetchTPrivKey(QString addr, const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "dumpprivkey"}, {"params", { addr.toStdString() }}, }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::importZPrivKey(QString addr, bool rescan, const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "z_importkey"}, {"params", { addr.toStdString(), (rescan? "yes" : "no") }}, }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::importTPrivKey(QString addr, bool rescan, const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "importprivkey"}, {"params", { addr.toStdString(), (rescan? "yes" : "no") }}, }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::validateAddress(QString address, const std::function& cb) { QString method = Settings::isZAddress(address) ? "z_validateaddress" : "validateaddress"; json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", method.toStdString() }, {"params", { address.toStdString() } }, }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::fetchBalance(const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "z_gettotalbalance"}, {"params", {0}} // Get Unconfirmed balance as well. }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::fetchTransactions(const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "listtransactions"} }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::sendZTransaction(json params, const std::function& cb, const std::function& err) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "z_sendmany"}, {"params", params} }; conn->doRPC(payload, cb, [=] (auto reply, auto parsed) { if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) { err(QString::fromStdString(parsed["error"]["message"])); } else { err(reply->errorString()); } }); } void ZcashdRPC::fetchInfo(const std::function& cb, const std::function& err) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "getinfo"} }; conn->doRPC(payload, cb, err); } void ZcashdRPC::fetchBlockchainInfo(const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "getblockchaininfo"} }; conn->doRPCIgnoreError(payload, cb); } void ZcashdRPC::fetchNetSolOps(const std::function cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "getnetworksolps"} }; conn->doRPCIgnoreError(payload, [=](const json& reply) { qint64 solrate = reply.get(); cb(solrate); }); } void ZcashdRPC::fetchMigrationStatus(const std::function& cb) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "z_getmigrationstatus"}, }; conn->doRPCWithDefaultErrorHandling(payload, cb); } void ZcashdRPC::setMigrationStatus(bool enabled) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "z_setmigration"}, {"params", {enabled}} }; conn->doRPCWithDefaultErrorHandling(payload, [=](json) { // Ignore return value. }); } /** * Method to get all the private keys for both z and t addresses. It will make 2 batch calls, * combine the result, and call the callback with a single list containing both the t-addr and z-addr * private keys */ void ZcashdRPC::fetchAllPrivKeys(const std::function>)> cb) { if (conn == nullptr) { // No connection, just return return; } // A special function that will call the callback when two lists have been added auto holder = new QPair>>(); holder->first = 0; // This is the number of times the callback has been called, initialized to 0 auto fnCombineTwoLists = [=] (QList> list) { // Increment the callback counter holder->first++; // Add all std::copy(list.begin(), list.end(), std::back_inserter(holder->second)); // And if the caller has been called twice, do the parent callback with the // collected list if (holder->first == 2) { // Sort so z addresses are on top std::sort(holder->second.begin(), holder->second.end(), [=] (auto a, auto b) { return a.first > b.first; }); cb(holder->second); delete holder; } }; // A utility fn to do the batch calling auto fnDoBatchGetPrivKeys = [=](json getAddressPayload, std::string privKeyDumpMethodName) { conn->doRPCWithDefaultErrorHandling(getAddressPayload, [=] (json resp) { QList addrs; for (auto addr : resp.get()) { addrs.push_back(QString::fromStdString(addr.get())); } // Then, do a batch request to get all the private keys conn->doBatchRPC( addrs, [=] (auto addr) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", privKeyDumpMethodName}, {"params", { addr.toStdString() }}, }; return payload; }, [=] (QMap* privkeys) { QList> allTKeys; for (QString addr: privkeys->keys()) { allTKeys.push_back( QPair( addr, QString::fromStdString(privkeys->value(addr).get()))); } fnCombineTwoLists(allTKeys); delete privkeys; } ); }); }; // First get all the t and z addresses. json payloadT = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "getaddressesbyaccount"}, {"params", {""} } }; json payloadZ = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "z_listaddresses"} }; fnDoBatchGetPrivKeys(payloadT, "dumpprivkey"); fnDoBatchGetPrivKeys(payloadZ, "z_exportkey"); } void ZcashdRPC::fetchOpStatus(const std::function& cb) { // Make an RPC to load pending operation statues json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, {"method", "z_getoperationstatus"}, }; conn->doRPCIgnoreError(payload, cb); } void ZcashdRPC::fetchReceivedTTrans(QList txids, QList sentZTxs, const std::function)> txdataFn) { // Look up all the txids to get the confirmation count for them. conn->doBatchRPC(txids, [=] (QString txid) { json payload = { {"jsonrpc", "1.0"}, {"id", "senttxid"}, {"method", "gettransaction"}, {"params", {txid.toStdString()}} }; return payload; }, [=] (QMap* txidList) { auto newSentZTxs = sentZTxs; // Update the original sent list with the confirmation count // TODO: This whole thing is kinda inefficient. We should probably just update the file // with the confirmed block number, so we don't have to keep calling gettransaction for the // sent items. for (TransactionItem& sentTx: newSentZTxs) { auto j = txidList->value(sentTx.txid); if (j.is_null()) continue; auto error = j["confirmations"].is_null(); if (!error) sentTx.confirmations = j["confirmations"].get(); } txdataFn(newSentZTxs); delete txidList; } ); } // Refresh received z txs by calling z_listreceivedbyaddress/gettransaction void ZcashdRPC::fetchReceivedZTrans(QList zaddrs, const std::function usedAddrFn, const std::function)> txdataFn) { // This method is complicated because z_listreceivedbyaddress only returns the txid, and // we have to make a follow up call to gettransaction to get details of that transaction. // Additionally, it has to be done in batches, because there are multiple z-Addresses, // and each z-Addr can have multiple received txs. // 1. For each z-Addr, get list of received txs conn->doBatchRPC(zaddrs, [=] (QString zaddr) { json payload = { {"jsonrpc", "1.0"}, {"id", "z_lrba"}, {"method", "z_listreceivedbyaddress"}, {"params", {zaddr.toStdString(), 0}} // Accept 0 conf as well. }; return payload; }, [=] (QMap* zaddrTxids) { // Process all txids, removing duplicates. This can happen if the same address // appears multiple times in a single tx's outputs. QSet txids; QMap memos; for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) { auto zaddr = it.key(); for (auto& i : it.value().get()) { // Mark the address as used usedAddrFn(zaddr); // Filter out change txs if (! i["change"].get()) { auto txid = QString::fromStdString(i["txid"].get()); txids.insert(txid); // Check for Memos QString memoBytes = QString::fromStdString(i["memo"].get()); if (!memoBytes.startsWith("f600")) { QString memo(QByteArray::fromHex( QByteArray::fromStdString(i["memo"].get()))); if (!memo.trimmed().isEmpty()) memos[zaddr + txid] = memo; } } } } // 2. For all txids, go and get the details of that txid. conn->doBatchRPC(txids.toList(), [=] (QString txid) { json payload = { {"jsonrpc", "1.0"}, {"id", "gettx"}, {"method", "gettransaction"}, {"params", {txid.toStdString()}} }; return payload; }, [=] (QMap* txidDetails) { QList txdata; // Combine them both together. For every zAddr's txid, get the amount, fee, confirmations and time for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) { for (auto& i : it.value().get()) { // Filter out change txs if (i["change"].get()) continue; auto zaddr = it.key(); auto txid = QString::fromStdString(i["txid"].get()); // Lookup txid in the map auto txidInfo = txidDetails->value(txid); qint64 timestamp; if (txidInfo.find("time") != txidInfo.end()) { timestamp = txidInfo["time"].get(); } else { timestamp = txidInfo["blocktime"].get(); } auto amount = i["amount"].get(); auto confirmations = static_cast(txidInfo["confirmations"].get()); TransactionItem tx{ QString("receive"), timestamp, zaddr, txid, amount, confirmations, "", memos.value(zaddr + txid, "") }; txdata.push_front(tx); } } txdataFn(txdata); // Cleanup both responses; delete zaddrTxids; delete txidDetails; } ); } ); }