Browse Source

Split rpc and controller

import_zecw
Aditya Kulkarni 5 years ago
parent
commit
c6f6b1c126
  1. 1
      .gitignore
  2. 1
      src/controller.h
  3. 314
      src/mainwindow.cpp
  4. 5
      src/mainwindow.h
  5. 591
      src/rpc.cpp
  6. 66
      src/rpc.h
  7. 479
      src/zcashdrpc.cpp
  8. 70
      src/zcashdrpc.h
  9. 6
      zec-qt-wallet.pro

1
.gitignore

@ -38,3 +38,4 @@ zcashd
IDEWorkspaceChecks.plist
*.sln
node_modules
zec-qt-wallet.pro.user.4.10-pre1

1
src/controller.h

@ -0,0 +1 @@
//

314
src/mainwindow.cpp

@ -223,207 +223,16 @@ void MainWindow::closeEvent(QCloseEvent* event) {
QMainWindow::closeEvent(event);
}
void MainWindow::turnstileProgress() {
Ui_TurnstileProgress progress;
QDialog d(this);
progress.setupUi(&d);
Settings::saveRestore(&d);
QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning);
progress.msgIcon->setPixmap(icon.pixmap(64, 64));
bool migrationFinished = false;
auto fnUpdateProgressUI = [=, &migrationFinished] () mutable {
// Get the plan progress
if (rpc->getTurnstile()->isMigrationPresent()) {
auto curProgress = rpc->getTurnstile()->getPlanProgress();
progress.progressTxt->setText(QString::number(curProgress.step) % QString(" / ") % QString::number(curProgress.totalSteps));
progress.progressBar->setValue(100 * curProgress.step / curProgress.totalSteps);
auto nextTxBlock = curProgress.nextBlock - Settings::getInstance()->getBlockNumber();
progress.fromAddr->setText(curProgress.from);
progress.toAddr->setText(curProgress.to);
if (curProgress.step == curProgress.totalSteps) {
migrationFinished = true;
auto txt = QString("Turnstile migration finished");
if (curProgress.hasErrors) {
txt = txt + ". There were some errors.\n\nYour funds are all in your wallet, so you should be able to finish moving them manually.";
}
progress.nextTx->setText(txt);
} else {
progress.nextTx->setText(QString("Next transaction in ")
% QString::number(nextTxBlock < 0 ? 0 : nextTxBlock)
% " blocks via " % curProgress.via % "\n"
% (nextTxBlock <= 0 ? "(waiting for confirmations)" : ""));
}
} else {
progress.progressTxt->setText("");
progress.progressBar->setValue(0);
progress.nextTx->setText("No turnstile migration is in progress");
}
};
QTimer progressTimer(this);
QObject::connect(&progressTimer, &QTimer::timeout, fnUpdateProgressUI);
progressTimer.start(Settings::updateSpeed);
fnUpdateProgressUI();
auto curProgress = rpc->getTurnstile()->getPlanProgress();
// Abort button
if (curProgress.step != curProgress.totalSteps)
progress.buttonBox->button(QDialogButtonBox::Discard)->setText("Abort");
else
progress.buttonBox->button(QDialogButtonBox::Discard)->setVisible(false);
// Abort button clicked
QObject::connect(progress.buttonBox->button(QDialogButtonBox::Discard), &QPushButton::clicked, [&] () {
if (curProgress.step != curProgress.totalSteps) {
auto abort = QMessageBox::warning(this, "Are you sure you want to Abort?",
"Are you sure you want to abort the migration?\nAll further transactions will be cancelled.\nAll your funds are still in your wallet.",
QMessageBox::Yes, QMessageBox::No);
if (abort == QMessageBox::Yes) {
rpc->getTurnstile()->removeFile();
d.close();
ui->statusBar->showMessage("Automatic Sapling turnstile migration aborted.");
}
}
});
d.exec();
if (migrationFinished || curProgress.step == curProgress.totalSteps) {
// Finished, so delete the file
rpc->getTurnstile()->removeFile();
}
}
void MainWindow::turnstileDoMigration(QString fromAddr) {
// If a migration is already in progress, show the progress dialog instead
if (rpc->getTurnstile()->isMigrationPresent()) {
turnstileProgress();
return;
}
Ui_Turnstile turnstile;
QDialog d(this);
turnstile.setupUi(&d);
Settings::saveRestore(&d);
QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation);
turnstile.msgIcon->setPixmap(icon.pixmap(64, 64));
auto fnGetAllSproutBalance = [=] () {
double bal = 0;
for (auto addr : rpc->getModel()->getAllZAddresses()) {
if (Settings::getInstance()->isSproutAddress(addr)) {
bal += rpc->getModel()->getAllBalances().value(addr);
}
}
return bal;
};
turnstile.fromBalance->setText(Settings::getZECUSDDisplayFormat(fnGetAllSproutBalance()));
for (auto addr : rpc->getModel()->getAllZAddresses()) {
auto bal = rpc->getModel()->getAllBalances().value(addr);
if (Settings::getInstance()->isSaplingAddress(addr)) {
turnstile.migrateTo->addItem(addr, bal);
} else {
turnstile.migrateZaddList->addItem(addr, bal);
}
}
auto fnUpdateSproutBalance = [=] (QString addr) {
double bal = 0;
// The currentText contains the balance as well, so strip that.
if (addr.contains("(")) {
addr = addr.left(addr.indexOf("("));
}
if (addr.startsWith("All")) {
bal = fnGetAllSproutBalance();
} else {
bal = rpc->getModel()->getAllBalances().value(addr);
}
auto balTxt = Settings::getZECUSDDisplayFormat(bal);
if (bal < Turnstile::minMigrationAmount) {
turnstile.fromBalance->setStyleSheet("color: red;");
turnstile.fromBalance->setText(balTxt % " [You need at least "
% Settings::getZECDisplayFormat(Turnstile::minMigrationAmount)
% " for automatic migration]");
turnstile.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
} else {
turnstile.fromBalance->setStyleSheet("");
turnstile.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
turnstile.fromBalance->setText(balTxt);
}
};
if (!fromAddr.isEmpty())
turnstile.migrateZaddList->setCurrentText(fromAddr);
fnUpdateSproutBalance(turnstile.migrateZaddList->currentText());
// Combo box selection event
QObject::connect(turnstile.migrateZaddList, &QComboBox::currentTextChanged, fnUpdateSproutBalance);
// Privacy level combobox
// Num tx over num blocks
QList<std::tuple<int, int>> privOptions;
privOptions.push_back(std::make_tuple<int, int>(3, 576));
privOptions.push_back(std::make_tuple<int, int>(5, 1152));
privOptions.push_back(std::make_tuple<int, int>(10, 2304));
QObject::connect(turnstile.privLevel, QOverload<int>::of(&QComboBox::currentIndexChanged), [=] (auto idx) {
// Update the fees
turnstile.minerFee->setText(
Settings::getZECUSDDisplayFormat(std::get<0>(privOptions[idx]) * Settings::getMinerFee()));
});
for (auto i : privOptions) {
turnstile.privLevel->addItem(QString::number((int)(std::get<1>(i) / 24 / 24)) % " days (" % // 24 blks/hr * 24 hrs per day
QString::number(std::get<1>(i)) % " blocks, ~" %
QString::number(std::get<0>(i)) % " txns)"
);
}
turnstile.buttonBox->button(QDialogButtonBox::Ok)->setText("Start");
if (d.exec() == QDialog::Accepted) {
auto privLevel = privOptions[turnstile.privLevel->currentIndex()];
rpc->getTurnstile()->planMigration(
turnstile.migrateZaddList->currentText(),
turnstile.migrateTo->currentText(),
std::get<0>(privLevel), std::get<1>(privLevel));
QMessageBox::information(this, "Backup your wallet.dat",
"The migration will now start. You can check progress in the File -> Sapling Turnstile menu.\n\nYOU MUST BACKUP YOUR wallet.dat NOW!\n\nNew Addresses have been added to your wallet which will be used for the migration.",
QMessageBox::Ok);
}
}
void MainWindow::setupTurnstileDialog() {
// Turnstile migration
QObject::connect(ui->actionTurnstile_Migration, &QAction::triggered, [=] () {
// If the underlying zcashd has support for the migration and there is no existing migration
// in progress, use that.
if (rpc->getMigrationStatus()->available && !rpc->getTurnstile()->isMigrationPresent()) {
if (rpc->getMigrationStatus()->available) {
Turnstile::showZcashdMigration(this);
} else {
// Else, show the ZecWallet turnstile tool
// If there is current migration that is present, show the progress button
if (rpc->getTurnstile()->isMigrationPresent())
turnstileProgress();
else
turnstileDoMigration();
// Else, do nothing
}
});
@ -718,109 +527,6 @@ void MainWindow::validateAddress() {
}
void MainWindow::postToZBoard() {
QDialog d(this);
Ui_zboard zb;
zb.setupUi(&d);
Settings::saveRestore(&d);
if (rpc->getConnection() == nullptr)
return;
// Fill the from field with sapling addresses.
for (auto i = rpc->getModel()->getAllBalances().keyBegin(); i != rpc->getModel()->getAllBalances().keyEnd(); i++) {
if (Settings::getInstance()->isSaplingAddress(*i) && rpc->getModel()->getAllBalances().value(*i) > 0) {
zb.fromAddr->addItem(*i);
}
}
QMap<QString, QString> topics;
// Insert the main topic automatically
topics.insert("#Main_Area", Settings::getInstance()->isTestnet() ? Settings::getDonationAddr() : Settings::getZboardAddr());
zb.topicsList->addItem(topics.firstKey());
// Then call the API to get topics, and if it returns successfully, then add the rest of the topics
rpc->getZboardTopics([&](QMap<QString, QString> topicsMap) {
for (auto t : topicsMap.keys()) {
topics.insert(t, Settings::getInstance()->isTestnet() ? Settings::getDonationAddr() : topicsMap[t]);
zb.topicsList->addItem(t);
}
});
// Testnet warning
if (Settings::getInstance()->isTestnet()) {
zb.testnetWarning->setText(tr("You are on testnet, your post won't actually appear on z-board.net"));
}
else {
zb.testnetWarning->setText("");
}
QRegExpValidator v(QRegExp("^[a-zA-Z0-9_]{3,20}$"), zb.postAs);
zb.postAs->setValidator(&v);
zb.feeAmount->setText(Settings::getZECUSDDisplayFormat(Settings::getZboardAmount() + Settings::getMinerFee()));
auto fnBuildNameMemo = [=]() -> QString {
auto memo = zb.memoTxt->toPlainText().trimmed();
if (!zb.postAs->text().trimmed().isEmpty())
memo = zb.postAs->text().trimmed() + ":: " + memo;
return memo;
};
auto fnUpdateMemoSize = [=]() {
QString txt = fnBuildNameMemo();
zb.memoSize->setText(QString::number(txt.toUtf8().size()) + "/512");
if (txt.toUtf8().size() <= 512) {
// Everything is fine
zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
zb.memoSize->setStyleSheet("");
}
else {
// Overweight
zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
zb.memoSize->setStyleSheet("color: red;");
}
// Disallow blank memos
if (zb.memoTxt->toPlainText().trimmed().isEmpty()) {
zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}
else {
zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}
};
// Memo text changed
QObject::connect(zb.memoTxt, &QPlainTextEdit::textChanged, fnUpdateMemoSize);
QObject::connect(zb.postAs, &QLineEdit::textChanged, fnUpdateMemoSize);
zb.memoTxt->setFocus();
fnUpdateMemoSize();
if (d.exec() == QDialog::Accepted) {
// Create a transaction.
Tx tx;
// Send from your first sapling address that has a balance.
tx.fromAddr = zb.fromAddr->currentText();
if (tx.fromAddr.isEmpty()) {
QMessageBox::critical(this, "Error Posting Message", tr("You need a sapling address with available balance to post"), QMessageBox::Ok);
return;
}
auto memo = zb.memoTxt->toPlainText().trimmed();
if (!zb.postAs->text().trimmed().isEmpty())
memo = zb.postAs->text().trimmed() + ":: " + memo;
auto toAddr = topics[zb.topicsList->currentText()];
tx.toAddrs.push_back(ToFields{ toAddr, Settings::getZboardAmount(), memo, memo.toUtf8().toHex() });
tx.fee = Settings::getMinerFee();
// And send the Tx
rpc->executeStandardUITransaction(tx);
}
}
void MainWindow::doImport(QList<QString>* keys) {
if (rpc->getConnection() == nullptr) {
// No connection, just return
@ -1095,7 +801,7 @@ void MainWindow::exportKeys(QString addr) {
};
if (allKeys) {
rpc->getAllPrivKeys(fnUpdateUIWithKeys);
rpc->fetchAllPrivKeys(fnUpdateUIWithKeys);
}
else {
auto fnAddKey = [=](json key) {
@ -1105,10 +811,10 @@ void MainWindow::exportKeys(QString addr) {
};
if (Settings::getInstance()->isZAddress(addr)) {
rpc->getZPrivKey(addr, fnAddKey);
rpc->fetchZPrivKey(addr, fnAddKey);
}
else {
rpc->getTPrivKey(addr, fnAddKey);
rpc->fetchTPrivKey(addr, fnAddKey);
}
}
@ -1195,12 +901,6 @@ void MainWindow::setupBalancesTab() {
});
}
if (Settings::getInstance()->isSproutAddress(addr)) {
menu.addAction(tr("Migrate to Sapling"), [=] () {
this->turnstileDoMigration(addr);
});
}
menu.exec(ui->balancesTable->viewport()->mapToGlobal(pos));
});
}
@ -1303,7 +1003,7 @@ void MainWindow::setupTransactionsTab() {
}
void MainWindow::addNewZaddr(bool sapling) {
rpc->newZaddr(sapling, [=] (json reply) {
rpc->createNewZaddr(sapling, [=] (json reply) {
QString addr = QString::fromStdString(reply.get<json::string_t>());
// Make sure the RPC class reloads the z-addrs for future use
rpc->refreshAddresses();
@ -1354,7 +1054,7 @@ std::function<void(bool)> MainWindow::addZAddrsToComboList(bool sapling) {
void MainWindow::setupReceiveTab() {
auto addNewTAddr = [=] () {
rpc->newTaddr([=] (json reply) {
rpc->createNewTaddr([=] (json reply) {
QString addr = QString::fromStdString(reply.get<json::string_t>());
// Make sure the RPC class reloads the t-addrs for future use
rpc->refreshAddresses();

5
src/mainwindow.h

@ -102,9 +102,6 @@ private:
Tx createTxFromSendPage();
bool confirmTx(Tx tx, RecurringPaymentInfo* rpi);
void turnstileDoMigration(QString fromAddr = "");
void turnstileProgress();
void cancelButton();
void sendButton();
void inputComboTextChanged(int index);
@ -124,7 +121,7 @@ private:
void donate();
void addressBook();
void postToZBoard();
//void postToZBoard();
void importPrivKey();
void exportAllKeys();
void exportKeys(QString addr = "");

591
src/rpc.cpp

@ -18,8 +18,6 @@ RPC::RPC(MainWindow* main) {
this->main = main;
this->ui = main->ui;
this->turnstile = new Turnstile(this, main);
// Setup balances table model
balancesTableModel = new BalancesTableModel(main->ui->balancesTable);
main->ui->balancesTable->setModel(balancesTableModel);
@ -54,6 +52,9 @@ RPC::RPC(MainWindow* main) {
// Create the data model
model = new DataModel();
// Crate the ZcashdRPC
zrpc = new ZcashdRPC();
// Initialize the migration status to unavailable.
this->migrationStatus.available = false;
}
@ -64,11 +65,9 @@ RPC::~RPC() {
delete transactionsTableModel;
delete balancesTableModel;
delete turnstile;
delete model;
delete conn;
delete zrpc;
}
void RPC::setEZcashd(QProcess* p) {
@ -83,8 +82,7 @@ void RPC::setEZcashd(QProcess* p) {
void RPC::setConnection(Connection* c) {
if (c == nullptr) return;
delete conn;
this->conn = c;
this->zrpc->setConnection(c);
ui->statusBar->showMessage("Ready!");
@ -106,254 +104,6 @@ void RPC::setConnection(Connection* c) {
refresh(true);
}
void RPC::getTAddresses(const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getaddressesbyaccount"},
{"params", {""}}
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::getZAddresses(const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "z_listaddresses"},
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::getTransparentUnspent(const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "listunspent"},
{"params", {0}} // Get UTXOs with 0 confirmations as well.
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::getZUnspent(const std::function<void(json)>& 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 RPC::newZaddr(bool sapling, const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "z_getnewaddress"},
{"params", { sapling ? "sapling" : "sprout" }},
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::newTaddr(const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getnewaddress"},
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::getZPrivKey(QString addr, const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "z_exportkey"},
{"params", { addr.toStdString() }},
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::getTPrivKey(QString addr, const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "dumpprivkey"},
{"params", { addr.toStdString() }},
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::importZPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "z_importkey"},
{"params", { addr.toStdString(), (rescan? "yes" : "no") }},
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::importTPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "importprivkey"},
{"params", { addr.toStdString(), (rescan? "yes" : "no") }},
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::validateAddress(QString address, const std::function<void(json)>& 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 RPC::getBalance(const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "z_gettotalbalance"},
{"params", {0}} // Get Unconfirmed balance as well.
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::getTransactions(const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "listtransactions"}
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void RPC::sendZTransaction(json params, const std::function<void(json)>& cb,
const std::function<void(QString)>& 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());
}
});
}
/**
* 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 RPC::getAllPrivKeys(const std::function<void(QList<QPair<QString, QString>>)> 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<int, QList<QPair<QString, QString>>>();
holder->first = 0; // This is the number of times the callback has been called, initialized to 0
auto fnCombineTwoLists = [=] (QList<QPair<QString, QString>> 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<QString> addrs;
for (auto addr : resp.get<json::array_t>()) {
addrs.push_back(QString::fromStdString(addr.get<json::string_t>()));
}
// Then, do a batch request to get all the private keys
conn->doBatchRPC<QString>(
addrs,
[=] (auto addr) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", privKeyDumpMethodName},
{"params", { addr.toStdString() }},
};
return payload;
},
[=] (QMap<QString, json>* privkeys) {
QList<QPair<QString, QString>> allTKeys;
for (QString addr: privkeys->keys()) {
allTKeys.push_back(
QPair<QString, QString>(
addr,
QString::fromStdString(privkeys->value(addr).get<json::string_t>())));
}
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");
}
// Build the RPC JSON Parameters for this tx
void RPC::fillTxJsonParams(json& params, Tx tx) {
@ -423,7 +173,7 @@ void RPC::noConnection() {
// Refresh received z txs by calling z_listreceivedbyaddress/gettransaction
void RPC::refreshReceivedZTrans(QList<QString> zaddrs) {
if (conn == nullptr)
if (!zrpc->haveConnection())
return noConnection();
// We'll only refresh the received Z txs if settings allows us.
@ -433,109 +183,19 @@ void RPC::refreshReceivedZTrans(QList<QString> zaddrs) {
return;
}
// 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<QString>(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<QString, json>* zaddrTxids) {
// Process all txids, removing duplicates. This can happen if the same address
// appears multiple times in a single tx's outputs.
QSet<QString> txids;
QMap<QString, QString> memos;
for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) {
auto zaddr = it.key();
for (auto& i : it.value().get<json::array_t>()) {
// Mark the address as used
model->markAddressUsed(zaddr);
// Filter out change txs
if (! i["change"].get<json::boolean_t>()) {
auto txid = QString::fromStdString(i["txid"].get<json::string_t>());
txids.insert(txid);
// Check for Memos
QString memoBytes = QString::fromStdString(i["memo"].get<json::string_t>());
if (!memoBytes.startsWith("f600")) {
QString memo(QByteArray::fromHex(
QByteArray::fromStdString(i["memo"].get<json::string_t>())));
if (!memo.trimmed().isEmpty())
memos[zaddr + txid] = memo;
}
}
}
}
// 2. For all txids, go and get the details of that txid.
conn->doBatchRPC<QString>(txids.toList(),
[=] (QString txid) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "gettx"},
{"method", "gettransaction"},
{"params", {txid.toStdString()}}
};
return payload;
},
[=] (QMap<QString, json>* txidDetails) {
QList<TransactionItem> 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<json::array_t>()) {
// Filter out change txs
if (i["change"].get<json::boolean_t>())
continue;
auto zaddr = it.key();
auto txid = QString::fromStdString(i["txid"].get<json::string_t>());
// Lookup txid in the map
auto txidInfo = txidDetails->value(txid);
qint64 timestamp;
if (txidInfo.find("time") != txidInfo.end()) {
timestamp = txidInfo["time"].get<json::number_unsigned_t>();
} else {
timestamp = txidInfo["blocktime"].get<json::number_unsigned_t>();
}
auto amount = i["amount"].get<json::number_float_t>();
auto confirmations = static_cast<long>(txidInfo["confirmations"].get<json::number_integer_t>());
TransactionItem tx{ QString("receive"), timestamp, zaddr, txid, amount,
confirmations, "", memos.value(zaddr + txid, "") };
txdata.push_front(tx);
}
}
transactionsTableModel->addZRecvData(txdata);
// Cleanup both responses;
delete zaddrTxids;
delete txidDetails;
}
);
}
zrpc->fetchReceivedZTrans(zaddrs,
[=] (QString addr) {
model->markAddressUsed(addr);
},
[=] (QList<TransactionItem> txdata) {
transactionsTableModel->addZRecvData(txdata);
}
);
}
/// This will refresh all the balance data from zcashd
void RPC::refresh(bool force) {
if (conn == nullptr)
if (!zrpc->haveConnection())
return noConnection();
getInfoThenRefresh(force);
@ -543,17 +203,12 @@ void RPC::refresh(bool force) {
void RPC::getInfoThenRefresh(bool force) {
if (conn == nullptr)
if (!zrpc->haveConnection())
return noConnection();
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getinfo"}
};
static bool prevCallSucceeded = false;
conn->doRPC(payload, [=] (const json& reply) {
zrpc->fetchInfo([=] (const json& reply) {
prevCallSucceeded = true;
// Testnet?
if (!reply["testnet"].is_null()) {
@ -580,9 +235,6 @@ void RPC::getInfoThenRefresh(bool force) {
// Something changed, so refresh everything.
lastBlock = curBlock;
// See if the turnstile migration has any steps that need to be done.
turnstile->executeMigrationStep();
refreshBalances();
refreshAddresses(); // This calls refreshZSentTransactions() and refreshReceivedZTrans()
refreshTransactions();
@ -600,28 +252,14 @@ void RPC::getInfoThenRefresh(bool force) {
// Get network sol/s
if (ezcashd) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getnetworksolps"}
};
conn->doRPCIgnoreError(payload, [=](const json& reply) {
qint64 solrate = reply.get<json::number_unsigned_t>();
zrpc->fetchNetSolOps([=] (qint64 solrate) {
ui->numconnections->setText(QString::number(connections));
ui->solrate->setText(QString::number(solrate) % " Sol/s");
});
}
// Call to see if the blockchain is syncing.
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getblockchaininfo"}
};
conn->doRPCIgnoreError(payload, [=](const json& reply) {
zrpc->fetchBlockchainInfo([=](const json& reply) {
auto progress = reply["verificationprogress"].get<double>();
bool isSyncing = progress < 0.9999; // 99.99%
int blockNumber = reply["blocks"].get<json::number_unsigned_t>();
@ -707,12 +345,12 @@ void RPC::getInfoThenRefresh(bool force) {
}
void RPC::refreshAddresses() {
if (conn == nullptr)
if (!zrpc->haveConnection())
return noConnection();
auto newzaddresses = new QList<QString>();
getZAddresses([=] (json reply) {
zrpc->fetchZAddresses([=] (json reply) {
for (auto& it : reply.get<json::array_t>()) {
auto addr = QString::fromStdString(it.get<json::string_t>());
newzaddresses->push_back(addr);
@ -727,7 +365,7 @@ void RPC::refreshAddresses() {
auto newtaddresses = new QList<QString>();
getTAddresses([=] (json reply) {
zrpc->fetchTAddresses([=] (json reply) {
for (auto& it : reply.get<json::array_t>()) {
auto addr = QString::fromStdString(it.get<json::string_t>());
if (Settings::isTAddress(addr))
@ -777,13 +415,7 @@ void RPC::refreshMigration() {
if (Settings::getInstance()->getZcashdVersion() < 2000552)
return;
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "z_getmigrationstatus"},
};
conn->doRPCWithDefaultErrorHandling(payload, [=](json reply) {
zrpc->fetchMigrationStatus([=](json reply) {
this->migrationStatus.available = true;
this->migrationStatus.enabled = reply["enabled"].get<json::boolean_t>();
this->migrationStatus.saplingAddress = QString::fromStdString(reply["destination_address"]);
@ -798,27 +430,12 @@ void RPC::refreshMigration() {
});
}
void RPC::setMigrationStatus(bool enabled) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "z_setmigration"},
{"params", {enabled}}
};
conn->doRPCWithDefaultErrorHandling(payload, [=](json) {
// Ignore return value.
});
}
void RPC::refreshBalances() {
if (conn == nullptr)
if (!zrpc->haveConnection())
return noConnection();
// 1. Get the Balances
getBalance([=] (json reply) {
zrpc->fetchBalance([=] (json reply) {
auto balT = QString::fromStdString(reply["transparent"]).toDouble();
auto balZ = QString::fromStdString(reply["private"]).toDouble();
auto balTotal = QString::fromStdString(reply["total"]).toDouble();
@ -841,10 +458,10 @@ void RPC::refreshBalances() {
auto newBalances = new QMap<QString, double>();
// Call the Transparent and Z unspent APIs serially and then, once they're done, update the UI
getTransparentUnspent([=] (json reply) {
zrpc->fetchTransparentUnspent([=] (json reply) {
auto anyTUnconfirmed = processUnspent(reply, newBalances, newUtxos);
getZUnspent([=] (json reply) {
zrpc->fetchZUnspent([=] (json reply) {
auto anyZUnconfirmed = processUnspent(reply, newBalances, newUtxos);
// Swap out the balances and UTXOs
@ -859,10 +476,10 @@ void RPC::refreshBalances() {
}
void RPC::refreshTransactions() {
if (conn == nullptr)
if (!zrpc->haveConnection())
return noConnection();
getTransactions([=] (json reply) {
zrpc->fetchTransactions([=] (json reply) {
QList<TransactionItem> txdata;
for (auto& it : reply.get<json::array_t>()) {
@ -894,7 +511,7 @@ void RPC::refreshTransactions() {
// Read sent Z transactions from the file.
void RPC::refreshSentZTrans() {
if (conn == nullptr)
if (!zrpc->haveConnection())
return noConnection();
auto sentZTxs = SentTxStore::readSentTxFile();
@ -913,36 +530,9 @@ void RPC::refreshSentZTrans() {
}
// Look up all the txids to get the confirmation count for them.
conn->doBatchRPC<QString>(txids,
[=] (QString txid) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "senttxid"},
{"method", "gettransaction"},
{"params", {txid.toStdString()}}
};
return payload;
},
[=] (QMap<QString, json>* 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<json::number_integer_t>();
}
transactionsTableModel->addZSentData(newSentZTxs);
delete txidList;
}
);
zrpc->fetchReceivedTTrans(txids, sentZTxs, [=](auto newSentZTxs) {
transactionsTableModel->addZSentData(newSentZTxs);
});
}
void RPC::addNewTxToWatch(const QString& newOpid, WatchedTx wtx) {
@ -985,7 +575,7 @@ void RPC::executeTransaction(Tx tx,
fillTxJsonParams(params, tx);
std::cout << std::setw(2) << params << std::endl;
sendZTransaction(params, [=](const json& reply) {
zrpc->sendZTransaction(params, [=](const json& reply) {
QString opid = QString::fromStdString(reply.get<json::string_t>());
// And then start monitoring the transaction
@ -999,17 +589,10 @@ void RPC::executeTransaction(Tx tx,
void RPC::watchTxStatus() {
if (conn == nullptr)
if (!zrpc->haveConnection())
return noConnection();
// Make an RPC to load pending operation statues
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "z_getoperationstatus"},
};
conn->doRPCIgnoreError(payload, [=] (const json& reply) {
zrpc->fetchOpStatus([=] (const json& reply) {
// There's an array for each item in the status
for (auto& it : reply.get<json::array_t>()) {
// If we were watching this Tx and its status became "success", then we'll show a status bar alert
@ -1058,7 +641,7 @@ void RPC::watchTxStatus() {
}
void RPC::checkForUpdate(bool silent) {
if (conn == nullptr)
if (!zrpc->haveConnection())
return noConnection();
QUrl cmcURL("https://api.github.com/repos/ZcashFoundation/zecwallet/releases");
@ -1066,7 +649,7 @@ void RPC::checkForUpdate(bool silent) {
QNetworkRequest req;
req.setUrl(cmcURL);
QNetworkReply *reply = conn->restclient->get(req);
QNetworkReply *reply = getConnection()->restclient->get(req);
QObject::connect(reply, &QNetworkReply::finished, [=] {
reply->deleteLater();
@ -1130,7 +713,7 @@ void RPC::checkForUpdate(bool silent) {
// Get the ZEC->USD price from coinmarketcap using their API
void RPC::refreshZECPrice() {
if (conn == nullptr)
if (!zrpc->haveConnection())
return noConnection();
QUrl cmcURL("https://api.coinmarketcap.com/v1/ticker/");
@ -1138,7 +721,7 @@ void RPC::refreshZECPrice() {
QNetworkRequest req;
req.setUrl(cmcURL);
QNetworkReply *reply = conn->restclient->get(req);
QNetworkReply *reply = getConnection()->restclient->get(req);
QObject::connect(reply, &QNetworkReply::finished, [=] {
reply->deleteLater();
@ -1184,7 +767,7 @@ void RPC::refreshZECPrice() {
void RPC::shutdownZcashd() {
// Shutdown embedded zcashd if it was started
if (ezcashd == nullptr || ezcashd->processId() == 0 || conn == nullptr) {
if (ezcashd == nullptr || ezcashd->processId() == 0 || ~zrpc->haveConnection()) {
// No zcashd running internally, just return
return;
}
@ -1195,8 +778,8 @@ void RPC::shutdownZcashd() {
{"method", "stop"}
};
conn->doRPCWithDefaultErrorHandling(payload, [=](auto) {});
conn->shutdown();
getConnection()->doRPCWithDefaultErrorHandling(payload, [=](auto) {});
getConnection()->shutdown();
QDialog d(main);
Ui_ConnectionDialog connD;
@ -1215,7 +798,7 @@ void RPC::shutdownZcashd() {
if ((ezcashd->atEnd() && ezcashd->processId() == 0) ||
waitCount > 30 ||
conn->config->zcashDaemon) { // If zcashd is daemon, then we don't have to do anything else
getConnection()->config->zcashDaemon) { // If zcashd is daemon, then we don't have to do anything else
qDebug() << "Ended";
waiter.stop();
QTimer::singleShot(1000, [&]() { d.accept(); });
@ -1238,59 +821,59 @@ void RPC::shutdownZcashd() {
}
// Fetch the Z-board topics list
void RPC::getZboardTopics(std::function<void(QMap<QString, QString>)> cb) {
if (conn == nullptr)
return noConnection();
// // Fetch the Z-board topics list
// void RPC::getZboardTopics(std::function<void(QMap<QString, QString>)> cb) {
// if (!zrpc->haveConnection())
// return noConnection();
QUrl cmcURL("http://z-board.net/listTopics");
// QUrl cmcURL("http://z-board.net/listTopics");
QNetworkRequest req;
req.setUrl(cmcURL);
// QNetworkRequest req;
// req.setUrl(cmcURL);
QNetworkReply *reply = conn->restclient->get(req);
// QNetworkReply *reply = conn->restclient->get(req);
QObject::connect(reply, &QNetworkReply::finished, [=] {
reply->deleteLater();
// QObject::connect(reply, &QNetworkReply::finished, [=] {
// reply->deleteLater();
try {
if (reply->error() != QNetworkReply::NoError) {
auto parsed = json::parse(reply->readAll(), nullptr, false);
if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
qDebug() << QString::fromStdString(parsed["error"]["message"]);
}
else {
qDebug() << reply->errorString();
}
return;
}
// try {
// if (reply->error() != QNetworkReply::NoError) {
// auto parsed = json::parse(reply->readAll(), nullptr, false);
// if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
// qDebug() << QString::fromStdString(parsed["error"]["message"]);
// }
// else {
// qDebug() << reply->errorString();
// }
// return;
// }
auto all = reply->readAll();
// auto all = reply->readAll();
auto parsed = json::parse(all, nullptr, false);
if (parsed.is_discarded()) {
return;
}
// auto parsed = json::parse(all, nullptr, false);
// if (parsed.is_discarded()) {
// return;
// }
QMap<QString, QString> topics;
for (const json& item : parsed["topics"].get<json::array_t>()) {
if (item.find("addr") == item.end() || item.find("topicName") == item.end())
return;
// QMap<QString, QString> topics;
// for (const json& item : parsed["topics"].get<json::array_t>()) {
// if (item.find("addr") == item.end() || item.find("topicName") == item.end())
// return;
QString addr = QString::fromStdString(item["addr"].get<json::string_t>());
QString topic = QString::fromStdString(item["topicName"].get<json::string_t>());
// QString addr = QString::fromStdString(item["addr"].get<json::string_t>());
// QString topic = QString::fromStdString(item["topicName"].get<json::string_t>());
topics.insert(topic, addr);
}
cb(topics);
}
catch (...) {
// If anything at all goes wrong, just set the price to 0 and move on.
qDebug() << QString("Caught something nasty");
}
});
}
// topics.insert(topic, addr);
// }
// cb(topics);
// }
// catch (...) {
// // If anything at all goes wrong, just set the price to 0 and move on.
// qDebug() << QString("Caught something nasty");
// }
// });
// }
/**
* Get a Sapling address from the user's wallet

66
src/rpc.h

@ -8,23 +8,11 @@
#include "txtablemodel.h"
#include "ui_mainwindow.h"
#include "mainwindow.h"
#include "zcashdrpc.h"
#include "connection.h"
using json = nlohmann::json;
class Turnstile;
struct TransactionItem {
QString type;
qint64 datetime;
QString address;
QString txid;
double amount;
long confirmations;
QString fromAddr;
QString memo;
};
struct WatchedTx {
QString opid;
Tx tx;
@ -49,19 +37,18 @@ public:
DataModel* getModel() { return model; }
Connection* getConnection() { return zrpc->getConnection(); }
void setConnection(Connection* c);
void setEZcashd(QProcess* p);
const QProcess* getEZcashD() { return ezcashd; }
void refresh(bool force = false);
void refreshAddresses();
void checkForUpdate(bool silent = true);
void refreshZECPrice();
void getZboardTopics(std::function<void(QMap<QString, QString>)> cb);
//void getZboardTopics(std::function<void(QMap<QString, QString>)> cb);
void executeStandardUITransaction(Tx tx);
@ -71,7 +58,7 @@ public:
const std::function<void(QString opid, QString errStr)> error);
void fillTxJsonParams(json& params, Tx tx);
void sendZTransaction(json params, const std::function<void(json)>& cb, const std::function<void(QString)>& err);
void watchTxStatus();
const QMap<QString, WatchedTx> getWatchingTxns() { return watchingOps; }
@ -79,30 +66,28 @@ public:
const TxTableModel* getTransactionsModel() { return transactionsTableModel; }
void newZaddr(bool sapling, const std::function<void(json)>& cb);
void newTaddr(const std::function<void(json)>& cb);
void getZPrivKey(QString addr, const std::function<void(json)>& cb);
void getTPrivKey(QString addr, const std::function<void(json)>& cb);
void importZPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb);
void importTPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb);
void validateAddress(QString address, const std::function<void(json)>& cb);
void shutdownZcashd();
void noConnection();
bool isEmbedded() { return ezcashd != nullptr; }
QString getDefaultSaplingAddress();
QString getDefaultTAddress();
void createNewZaddr(bool sapling, const std::function<void(json)>& cb) { zrpc->createNewZaddr(sapling, cb); }
void createNewTaddr(const std::function<void(json)>& cb) { zrpc->createNewTaddr(cb); }
void getAllPrivKeys(const std::function<void(QList<QPair<QString, QString>>)>);
void validateAddress(QString address, const std::function<void(json)>& cb) { zrpc->validateAddress(address, cb); }
Turnstile* getTurnstile() { return turnstile; }
Connection* getConnection() { return conn; }
void fetchZPrivKey(QString addr, const std::function<void(json)>& cb) { zrpc->fetchZPrivKey(addr, cb); }
void fetchTPrivKey(QString addr, const std::function<void(json)>& cb) { zrpc->fetchTPrivKey(addr, cb); }
void fetchAllPrivKeys(const std::function<void(QList<QPair<QString, QString>>)> cb) { zrpc->fetchAllPrivKeys(cb); }
const MigrationStatus* getMigrationStatus() { return &migrationStatus; }
void setMigrationStatus(bool enabled);
void importZPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb) { zrpc->importZPrivKey(addr, rescan, cb); }
void importTPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb) { zrpc->importTPrivKey(addr, rescan, cb); }
QString getDefaultSaplingAddress();
QString getDefaultTAddress();
const MigrationStatus* getMigrationStatus() { return &migrationStatus; }
void setMigrationStatus(bool status) { zrpc->setMigrationStatus(status); }
private:
void refreshBalances();
@ -115,16 +100,7 @@ private:
void updateUI (bool anyUnconfirmed);
void getInfoThenRefresh(bool force);
void getBalance(const std::function<void(json)>& cb);
void getTransparentUnspent (const std::function<void(json)>& cb);
void getZUnspent (const std::function<void(json)>& cb);
void getTransactions (const std::function<void(json)>& cb);
void getZAddresses (const std::function<void(json)>& cb);
void getTAddresses (const std::function<void(json)>& cb);
Connection* conn = nullptr;
QProcess* ezcashd = nullptr;
QMap<QString, WatchedTx> watchingOps;
@ -133,6 +109,7 @@ private:
BalancesTableModel* balancesTableModel = nullptr;
DataModel* model;
ZcashdRPC* zrpc;
QTimer* timer;
QTimer* txTimer;
@ -140,7 +117,6 @@ private:
Ui::MainWindow* ui;
MainWindow* main;
Turnstile* turnstile;
// Sapling turnstile migration status (for the zcashd v2.0.5 tool)
MigrationStatus migrationStatus;

479
src/zcashdrpc.cpp

@ -0,0 +1,479 @@
#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<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getaddressesbyaccount"},
{"params", {""}}
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void ZcashdRPC::fetchZAddresses(const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "z_listaddresses"},
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void ZcashdRPC::fetchTransparentUnspent(const std::function<void(json)>& 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<void(json)>& 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<void(json)>& 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<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getnewaddress"},
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void ZcashdRPC::fetchZPrivKey(QString addr, const std::function<void(json)>& 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<void(json)>& 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<void(json)>& 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<void(json)>& 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<void(json)>& 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<void(json)>& 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<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "listtransactions"}
};
conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void ZcashdRPC::sendZTransaction(json params, const std::function<void(json)>& cb,
const std::function<void(QString)>& 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<void(json)>& cb,
const std::function<void(QNetworkReply*, const json&)>& err) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getinfo"}
};
conn->doRPC(payload, cb, err);
}
void ZcashdRPC::fetchBlockchainInfo(const std::function<void(json)>& cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getblockchaininfo"}
};
conn->doRPCIgnoreError(payload, cb);
}
void ZcashdRPC::fetchNetSolOps(const std::function<void(qint64)> cb) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", "getnetworksolps"}
};
conn->doRPCIgnoreError(payload, [=](const json& reply) {
qint64 solrate = reply.get<json::number_unsigned_t>();
cb(solrate);
});
}
void ZcashdRPC::fetchMigrationStatus(const std::function<void(json)>& 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<void(QList<QPair<QString, QString>>)> 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<int, QList<QPair<QString, QString>>>();
holder->first = 0; // This is the number of times the callback has been called, initialized to 0
auto fnCombineTwoLists = [=] (QList<QPair<QString, QString>> 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<QString> addrs;
for (auto addr : resp.get<json::array_t>()) {
addrs.push_back(QString::fromStdString(addr.get<json::string_t>()));
}
// Then, do a batch request to get all the private keys
conn->doBatchRPC<QString>(
addrs,
[=] (auto addr) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"method", privKeyDumpMethodName},
{"params", { addr.toStdString() }},
};
return payload;
},
[=] (QMap<QString, json>* privkeys) {
QList<QPair<QString, QString>> allTKeys;
for (QString addr: privkeys->keys()) {
allTKeys.push_back(
QPair<QString, QString>(
addr,
QString::fromStdString(privkeys->value(addr).get<json::string_t>())));
}
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<void(json)>& 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<QString> txids, QList<TransactionItem> sentZTxs,
const std::function<void(QList<TransactionItem>)> txdataFn) {
// Look up all the txids to get the confirmation count for them.
conn->doBatchRPC<QString>(txids,
[=] (QString txid) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "senttxid"},
{"method", "gettransaction"},
{"params", {txid.toStdString()}}
};
return payload;
},
[=] (QMap<QString, json>* 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<json::number_integer_t>();
}
txdataFn(newSentZTxs);
delete txidList;
}
);
}
// Refresh received z txs by calling z_listreceivedbyaddress/gettransaction
void ZcashdRPC::fetchReceivedZTrans(QList<QString> zaddrs, const std::function<void(QString)> usedAddrFn,
const std::function<void(QList<TransactionItem>)> 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<QString>(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<QString, json>* zaddrTxids) {
// Process all txids, removing duplicates. This can happen if the same address
// appears multiple times in a single tx's outputs.
QSet<QString> txids;
QMap<QString, QString> memos;
for (auto it = zaddrTxids->constBegin(); it != zaddrTxids->constEnd(); it++) {
auto zaddr = it.key();
for (auto& i : it.value().get<json::array_t>()) {
// Mark the address as used
usedAddrFn(zaddr);
// Filter out change txs
if (! i["change"].get<json::boolean_t>()) {
auto txid = QString::fromStdString(i["txid"].get<json::string_t>());
txids.insert(txid);
// Check for Memos
QString memoBytes = QString::fromStdString(i["memo"].get<json::string_t>());
if (!memoBytes.startsWith("f600")) {
QString memo(QByteArray::fromHex(
QByteArray::fromStdString(i["memo"].get<json::string_t>())));
if (!memo.trimmed().isEmpty())
memos[zaddr + txid] = memo;
}
}
}
}
// 2. For all txids, go and get the details of that txid.
conn->doBatchRPC<QString>(txids.toList(),
[=] (QString txid) {
json payload = {
{"jsonrpc", "1.0"},
{"id", "gettx"},
{"method", "gettransaction"},
{"params", {txid.toStdString()}}
};
return payload;
},
[=] (QMap<QString, json>* txidDetails) {
QList<TransactionItem> 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<json::array_t>()) {
// Filter out change txs
if (i["change"].get<json::boolean_t>())
continue;
auto zaddr = it.key();
auto txid = QString::fromStdString(i["txid"].get<json::string_t>());
// Lookup txid in the map
auto txidInfo = txidDetails->value(txid);
qint64 timestamp;
if (txidInfo.find("time") != txidInfo.end()) {
timestamp = txidInfo["time"].get<json::number_unsigned_t>();
} else {
timestamp = txidInfo["blocktime"].get<json::number_unsigned_t>();
}
auto amount = i["amount"].get<json::number_float_t>();
auto confirmations = static_cast<long>(txidInfo["confirmations"].get<json::number_integer_t>());
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;
}
);
}
);
}

70
src/zcashdrpc.h

@ -0,0 +1,70 @@
#ifndef ZCASHDRPC_H
#define ZCASHDRPC_H
#include "precompiled.h"
#include "connection.h"
using json = nlohmann::json;
struct TransactionItem {
QString type;
qint64 datetime;
QString address;
QString txid;
double amount;
long confirmations;
QString fromAddr;
QString memo;
};
class ZcashdRPC {
public:
ZcashdRPC();
~ZcashdRPC();
bool haveConnection();
void setConnection(Connection* c);
Connection* getConnection() { return conn; }
void fetchTransparentUnspent (const std::function<void(json)>& cb);
void fetchZUnspent (const std::function<void(json)>& cb);
void fetchTransactions (const std::function<void(json)>& cb);
void fetchZAddresses (const std::function<void(json)>& cb);
void fetchTAddresses (const std::function<void(json)>& cb);
void fetchReceivedZTrans(QList<QString> zaddrs, const std::function<void(QString)> usedAddrFn,
const std::function<void(QList<TransactionItem>)> txdataFn);
void fetchReceivedTTrans(QList<QString> txids, QList<TransactionItem> sentZtxs,
const std::function<void(QList<TransactionItem>)> txdataFn);
void fetchInfo(const std::function<void(json)>& cb,
const std::function<void(QNetworkReply*, const json&)>& err);
void fetchBlockchainInfo(const std::function<void(json)>& cb);
void fetchNetSolOps(const std::function<void(qint64)> cb);
void fetchOpStatus(const std::function<void(json)>& cb);
void fetchMigrationStatus(const std::function<void(json)>& cb);
void setMigrationStatus(bool enabled);
void fetchBalance(const std::function<void(json)>& cb);
void createNewZaddr(bool sapling, const std::function<void(json)>& cb);
void createNewTaddr(const std::function<void(json)>& cb);
void fetchZPrivKey(QString addr, const std::function<void(json)>& cb);
void fetchTPrivKey(QString addr, const std::function<void(json)>& cb);
void importZPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb);
void importTPrivKey(QString addr, bool rescan, const std::function<void(json)>& cb);
void validateAddress(QString address, const std::function<void(json)>& cb);
void fetchAllPrivKeys(const std::function<void(QList<QPair<QString, QString>>)>);
void sendZTransaction(json params, const std::function<void(json)>& cb, const std::function<void(QString)>& err);
private:
Connection* conn = nullptr;
};
#endif // ZCASHDRPC_H

6
zec-qt-wallet.pro

@ -62,7 +62,8 @@ SOURCES += \
src/memoedit.cpp \
src/viewalladdresses.cpp \
src/datamodel.cpp \
src/controller.cpp
src/controller.cpp \
src/zcashdrpc.cpp
HEADERS += \
src/mainwindow.h \
@ -91,7 +92,8 @@ HEADERS += \
src/memoedit.h \
src/viewalladdresses.h \
src/datamodel.h \
src/controller.h
src/controller.h \
src/zcashdrpc.h
FORMS += \
src/mainwindow.ui \

Loading…
Cancel
Save