diff --git a/doc/release-notes.md b/doc/release-notes.md
index d2e5bebf6..6e51be773 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -1,7 +1,17 @@
Bitcoin ABC version 0.18.6 is now available from:
This release includes the following features and fixes:
- Add `getfinalizedblock` rpc to allow node operators to introspec
- the current finalized block.
\ No newline at end of file
+ the current finalized block.
+ - Wallet `getnewaddress` and `addmultisigaddress` RPC `account` named
+ parameters have been renamed to `label` with no change in behavior.
+ - Wallet `getlabeladdress`, `getreceivedbylabel`, `listreceivedbylabel`, and
+ `setlabel` RPCs have been added to replace `getaccountaddress`,
+ `getreceivedbyaccount`, `listreceivedbyaccount`, and `setaccount` RPCs,
+ which are now deprecated. There is no change in behavior between the
+ new RPCs and deprecated RPCs.
+ - Wallet `listreceivedbylabel`, `listreceivedbyaccount` and `listunspent` RPCs
+ add `label` fields to returned JSON objects that previously only had
+ `account` fields.
diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp
index 713426b26..93457e79f 100644
--- a/src/qt/paymentserver.cpp
+++ b/src/qt/paymentserver.cpp
@@ -1,881 +1,880 @@
// Copyright (c) 2011-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "paymentserver.h"
#include "bitcoinunits.h"
#include "guiutil.h"
#include "optionsmodel.h"
#include "chainparams.h"
#include "config.h"
#include "dstencode.h"
#include "policy/policy.h"
#include "ui_interface.h"
#include "util.h"
#include "wallet/wallet.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
// BIP70 payment protocol messages
const char *BIP70_MESSAGE_PAYMENTACK = "PaymentACK";
const char *BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest";
// BIP71 payment protocol media types
const char *BIP71_MIMETYPE_PAYMENT = "application/bitcoincash-payment";
const char *BIP71_MIMETYPE_PAYMENTACK = "application/bitcoincash-paymentack";
const char *BIP71_MIMETYPE_PAYMENTREQUEST =
"application/bitcoincash-paymentrequest";
struct X509StoreDeleter {
void operator()(X509_STORE *b) { X509_STORE_free(b); }
};
struct X509Deleter {
void operator()(X509 *b) { X509_free(b); }
};
namespace // Anon namespace
{
std::unique_ptr certStore;
}
//
// Create a name that is unique for:
// testnet / non-testnet
// data directory
//
static QString ipcServerName() {
QString name("BitcoinQt");
// Append a simple hash of the datadir
// Note that GetDataDir(true) returns a different path for -testnet versus
// main net
QString ddir(GUIUtil::boostPathToQString(GetDataDir(true)));
name.append(QString::number(qHash(ddir)));
return name;
}
//
// We store payment URIs and requests received before the main GUI window is up
// and ready to ask the user to send payment.
//
static QList savedPaymentRequests;
static void ReportInvalidCertificate(const QSslCertificate &cert) {
qDebug() << QString("%1: Payment server found an invalid certificate: ")
.arg(__func__)
<< cert.serialNumber()
<< cert.subjectInfo(QSslCertificate::CommonName)
<< cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier)
<< cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
}
//
// Load OpenSSL's list of root certificate authorities
//
void PaymentServer::LoadRootCAs(X509_STORE *_store) {
// Unit tests mostly use this, to pass in fake root CAs:
if (_store) {
certStore.reset(_store);
return;
}
// Normal execution, use either -rootcertificates or system certs:
certStore.reset(X509_STORE_new());
// Note: use "-system-" default here so that users can pass
// -rootcertificates="" and get 'I don't like X.509 certificates, don't
// trust anybody' behavior:
QString certFile =
QString::fromStdString(gArgs.GetArg("-rootcertificates", "-system-"));
// Empty store
if (certFile.isEmpty()) {
qDebug() << QString("PaymentServer::%1: Payment request authentication "
"via X.509 certificates disabled.")
.arg(__func__);
return;
}
QList certList;
if (certFile != "-system-") {
qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root "
"certificate.")
.arg(__func__)
.arg(certFile);
certList = QSslCertificate::fromPath(certFile);
// Use those certificates when fetching payment requests, too:
QSslSocket::setDefaultCaCertificates(certList);
} else
certList = QSslSocket::systemCaCertificates();
int nRootCerts = 0;
const QDateTime currentTime = QDateTime::currentDateTime();
for (const QSslCertificate &cert : certList) {
// Don't log nullptr certificates
if (cert.isNull()) {
continue;
}
// Not yet active/valid, or expired certificate
if (currentTime < cert.effectiveDate() ||
currentTime > cert.expiryDate()) {
ReportInvalidCertificate(cert);
continue;
}
// Blacklisted certificate
if (cert.isBlacklisted()) {
ReportInvalidCertificate(cert);
continue;
}
QByteArray certData = cert.toDer();
const uint8_t *data = (const uint8_t *)certData.data();
std::unique_ptr x509(
d2i_X509(0, &data, certData.size()));
if (x509 && X509_STORE_add_cert(certStore.get(), x509.get())) {
// Note: X509_STORE increases the reference count to the X509
// object, we still have to release our reference to it.
++nRootCerts;
} else {
ReportInvalidCertificate(cert);
continue;
}
}
qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts
<< " root certificates";
// Project for another day:
// Fetch certificate revocation lists, and add them to certStore.
// Issues to consider:
// performance (start a thread to fetch in background?)
// privacy (fetch through tor/proxy so IP address isn't revealed)
// would it be easier to just use a compiled-in blacklist?
// or use Qt's blacklist?
// "certificate stapling" with server-side caching is more efficient
}
static std::string ipcParseURI(const QString &arg, const CChainParams ¶ms,
bool useCashAddr) {
const QString scheme = GUIUtil::bitcoinURIScheme(params, useCashAddr);
if (!arg.startsWith(scheme + ":", Qt::CaseInsensitive)) {
return {};
}
SendCoinsRecipient r;
if (!GUIUtil::parseBitcoinURI(scheme, arg, &r)) {
return {};
}
return r.address.toStdString();
}
static bool ipcCanParseCashAddrURI(const QString &arg,
const std::string &network) {
auto tempChainParams = CreateChainParams(network);
std::string addr = ipcParseURI(arg, *tempChainParams, true);
return IsValidDestinationString(addr, *tempChainParams);
}
static bool ipcCanParseLegacyURI(const QString &arg,
const std::string &network) {
auto tempChainParams = CreateChainParams(network);
std::string addr = ipcParseURI(arg, *tempChainParams, false);
return IsValidDestinationString(addr, *tempChainParams);
}
//
// Sending to the server is done synchronously, at startup.
// If the server isn't already running, startup continues, and the items in
// savedPaymentRequest will be handled when uiReady() is called.
//
// Warning: ipcSendCommandLine() is called early in init, so don't use "Q_EMIT
// message()", but "QMessageBox::"!
//
void PaymentServer::ipcParseCommandLine(int argc, char *argv[]) {
std::array networks = {
{&CBaseChainParams::MAIN, &CBaseChainParams::TESTNET,
&CBaseChainParams::REGTEST}};
const std::string *chosenNetwork = nullptr;
for (int i = 1; i < argc; i++) {
QString arg(argv[i]);
if (arg.startsWith("-")) {
continue;
}
const std::string *itemNetwork = nullptr;
// Try to parse as a URI
for (auto net : networks) {
if (ipcCanParseCashAddrURI(arg, *net)) {
itemNetwork = net;
break;
}
if (ipcCanParseLegacyURI(arg, *net)) {
itemNetwork = net;
break;
}
}
if (!itemNetwork && QFile::exists(arg)) {
// Filename
PaymentRequestPlus request;
if (readPaymentRequestFromFile(arg, request)) {
for (auto net : networks) {
if (*net == request.getDetails().network()) {
itemNetwork = net;
}
}
}
}
if (itemNetwork == nullptr) {
// Printing to debug.log is about the best we can do here, the GUI
// hasn't started yet so we can't pop up a message box.
qWarning() << "PaymentServer::ipcSendCommandLine: Payment request "
"file or URI does not exist or is invalid: "
<< arg;
continue;
}
if (chosenNetwork && chosenNetwork != itemNetwork) {
qWarning() << "PaymentServer::ipcSendCommandLine: Payment request "
"from network "
<< QString(itemNetwork->c_str())
<< " does not match already chosen network "
<< QString(chosenNetwork->c_str());
continue;
}
savedPaymentRequests.append(arg);
chosenNetwork = itemNetwork;
}
if (chosenNetwork) {
SelectParams(*chosenNetwork);
}
}
//
// Sending to the server is done synchronously, at startup.
// If the server isn't already running, startup continues, and the items in
// savedPaymentRequest will be handled when uiReady() is called.
//
bool PaymentServer::ipcSendCommandLine() {
bool fResult = false;
for (const QString &r : savedPaymentRequests) {
QLocalSocket *socket = new QLocalSocket();
socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT)) {
delete socket;
socket = nullptr;
return false;
}
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << r;
out.device()->seek(0);
socket->write(block);
socket->flush();
socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
socket->disconnectFromServer();
delete socket;
socket = nullptr;
fResult = true;
}
return fResult;
}
PaymentServer::PaymentServer(QObject *parent, bool startLocalServer)
: QObject(parent), saveURIs(true), uriServer(0), netManager(0),
optionsModel(0) {
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
// Install global event filter to catch QFileOpenEvents
// on Mac: sent when you click bitcoincash: links
// other OSes: helpful when dealing with payment request files
if (parent) {
parent->installEventFilter(this);
}
QString name = ipcServerName();
// Clean up old socket leftover from a crash:
QLocalServer::removeServer(name);
if (startLocalServer) {
uriServer = new QLocalServer(this);
if (!uriServer->listen(name)) {
// constructor is called early in init, so don't use "Q_EMIT
// message()" here
QMessageBox::critical(0, tr("Payment request error"),
tr("Cannot start click-to-pay handler"));
} else {
connect(uriServer, SIGNAL(newConnection()), this,
SLOT(handleURIConnection()));
connect(this, SIGNAL(receivedPaymentACK(QString)), this,
SLOT(handlePaymentACK(QString)));
}
}
}
PaymentServer::~PaymentServer() {
google::protobuf::ShutdownProtobufLibrary();
}
//
// OSX-specific way of handling bitcoincash: URIs and PaymentRequest mime types.
// Also used by paymentservertests.cpp and when opening a payment request file
// via "Open URI..." menu entry.
//
bool PaymentServer::eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent *fileEvent = static_cast(event);
if (!fileEvent->file().isEmpty()) {
handleURIOrFile(fileEvent->file());
} else if (!fileEvent->url().isEmpty()) {
handleURIOrFile(fileEvent->url().toString());
}
return true;
}
return QObject::eventFilter(object, event);
}
void PaymentServer::initNetManager() {
if (!optionsModel) {
return;
}
if (netManager != nullptr) {
delete netManager;
}
// netManager is used to fetch paymentrequests given in bitcoincash: URIs
netManager = new QNetworkAccessManager(this);
QNetworkProxy proxy;
// Query active SOCKS5 proxy
if (optionsModel->getProxySettings(proxy)) {
netManager->setProxy(proxy);
qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy"
<< proxy.hostName() << ":" << proxy.port();
} else {
qDebug()
<< "PaymentServer::initNetManager: No active proxy server found.";
}
connect(netManager, SIGNAL(finished(QNetworkReply *)), this,
SLOT(netRequestFinished(QNetworkReply *)));
connect(netManager,
SIGNAL(sslErrors(QNetworkReply *, const QList &)), this,
SLOT(reportSslErrors(QNetworkReply *, const QList &)));
}
void PaymentServer::uiReady() {
initNetManager();
saveURIs = false;
for (const QString &s : savedPaymentRequests) {
handleURIOrFile(s);
}
savedPaymentRequests.clear();
}
bool PaymentServer::handleURI(const QString &scheme, const QString &s) {
if (!s.startsWith(scheme + ":", Qt::CaseInsensitive)) {
return false;
}
QUrlQuery uri((QUrl(s)));
if (uri.hasQueryItem("r")) {
// payment request URI
QByteArray temp;
temp.append(uri.queryItemValue("r"));
QString decoded = QUrl::fromPercentEncoding(temp);
QUrl fetchUrl(decoded, QUrl::StrictMode);
if (fetchUrl.isValid()) {
qDebug() << "PaymentServer::handleURIOrFile: fetchRequest("
<< fetchUrl << ")";
fetchRequest(fetchUrl);
} else {
qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: "
<< fetchUrl;
Q_EMIT message(tr("URI handling"),
tr("Payment request fetch URL is invalid: %1")
.arg(fetchUrl.toString()),
CClientUIInterface::ICON_WARNING);
}
return true;
}
// normal URI
SendCoinsRecipient recipient;
if (GUIUtil::parseBitcoinURI(scheme, s, &recipient)) {
if (!IsValidDestinationString(recipient.address.toStdString(),
GetConfig().GetChainParams())) {
Q_EMIT message(
tr("URI handling"),
tr("Invalid payment address %1").arg(recipient.address),
CClientUIInterface::MSG_ERROR);
} else {
Q_EMIT receivedPaymentRequest(recipient);
}
} else {
Q_EMIT message(
tr("URI handling"),
tr("URI cannot be parsed! This can be caused by an invalid "
"Bitcoin address or malformed URI parameters."),
CClientUIInterface::ICON_WARNING);
}
return true;
}
void PaymentServer::handleURIOrFile(const QString &s) {
if (saveURIs) {
savedPaymentRequests.append(s);
return;
}
// bitcoincash: CashAddr URI
QString schemeCash = GUIUtil::bitcoinURIScheme(Params(), true);
if (handleURI(schemeCash, s)) {
return;
}
// bitcoincash: Legacy URI
QString schemeLegacy = GUIUtil::bitcoinURIScheme(Params(), false);
if (handleURI(schemeLegacy, s)) {
return;
}
// payment request file
if (QFile::exists(s)) {
PaymentRequestPlus request;
SendCoinsRecipient recipient;
if (!readPaymentRequestFromFile(s, request)) {
Q_EMIT message(tr("Payment request file handling"),
tr("Payment request file cannot be read! This can "
"be caused by an invalid payment request file."),
CClientUIInterface::ICON_WARNING);
} else if (processPaymentRequest(request, recipient)) {
Q_EMIT receivedPaymentRequest(recipient);
}
return;
}
}
void PaymentServer::handleURIConnection() {
QLocalSocket *clientConnection = uriServer->nextPendingConnection();
while (clientConnection->bytesAvailable() < (int)sizeof(quint32)) {
clientConnection->waitForReadyRead();
}
connect(clientConnection, SIGNAL(disconnected()), clientConnection,
SLOT(deleteLater()));
QDataStream in(clientConnection);
in.setVersion(QDataStream::Qt_4_0);
if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
return;
}
QString msg;
in >> msg;
handleURIOrFile(msg);
}
//
// Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine()
// so don't use "Q_EMIT message()", but "QMessageBox::"!
//
bool PaymentServer::readPaymentRequestFromFile(const QString &filename,
PaymentRequestPlus &request) {
QFile f(filename);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << QString("PaymentServer::%1: Failed to open %2")
.arg(__func__)
.arg(filename);
return false;
}
// BIP70 DoS protection
if (!verifySize(f.size())) {
return false;
}
QByteArray data = f.readAll();
return request.parse(data);
}
bool PaymentServer::processPaymentRequest(const PaymentRequestPlus &request,
SendCoinsRecipient &recipient) {
if (!optionsModel) {
return false;
}
if (request.IsInitialized()) {
// Payment request network matches client network?
if (!verifyNetwork(request.getDetails())) {
Q_EMIT message(
tr("Payment request rejected"),
tr("Payment request network doesn't match client network."),
CClientUIInterface::MSG_ERROR);
return false;
}
// Make sure any payment requests involved are still valid.
// This is re-checked just before sending coins in
// WalletModel::sendCoins().
if (verifyExpired(request.getDetails())) {
Q_EMIT message(tr("Payment request rejected"),
tr("Payment request expired."),
CClientUIInterface::MSG_ERROR);
return false;
}
} else {
Q_EMIT message(tr("Payment request error"),
tr("Payment request is not initialized."),
CClientUIInterface::MSG_ERROR);
return false;
}
recipient.paymentRequest = request;
recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo());
request.getMerchant(certStore.get(), recipient.authenticatedMerchant);
QList> sendingTos = request.getPayTo();
QStringList addresses;
for (const std::pair &sendingTo : sendingTos) {
// Extract and check destination addresses
CTxDestination dest;
if (ExtractDestination(sendingTo.first, dest)) {
// Append destination address
addresses.append(QString::fromStdString(EncodeDestination(dest)));
} else if (!recipient.authenticatedMerchant.isEmpty()) {
// Unauthenticated payment requests to custom bitcoin addresses are
// not supported
// (there is no good way to tell the user where they are paying in a
// way they'd
// have a chance of understanding).
Q_EMIT message(tr("Payment request rejected"),
tr("Unverified payment requests to custom payment "
"scripts are unsupported."),
CClientUIInterface::MSG_ERROR);
return false;
}
// Bitcoin amounts are stored as (optional) uint64 in the protobuf
// messages (see paymentrequest.proto), but Amount is defined as
// int64_t. Because of that we need to verify that amounts are in a
// valid range and no overflow has happened.
if (!verifyAmount(sendingTo.second)) {
Q_EMIT message(tr("Payment request rejected"),
tr("Invalid payment request."),
CClientUIInterface::MSG_ERROR);
return false;
}
// Extract and check amounts
CTxOut txOut(Amount(sendingTo.second), sendingTo.first);
if (txOut.IsDust(dustRelayFee)) {
Q_EMIT message(
tr("Payment request error"),
tr("Requested payment amount of %1 is too small (considered "
"dust).")
.arg(BitcoinUnits::formatWithUnit(
optionsModel->getDisplayUnit(), sendingTo.second)),
CClientUIInterface::MSG_ERROR);
return false;
}
recipient.amount += sendingTo.second;
// Also verify that the final amount is still in a valid range after
// adding additional amounts.
if (!verifyAmount(recipient.amount)) {
Q_EMIT message(tr("Payment request rejected"),
tr("Invalid payment request."),
CClientUIInterface::MSG_ERROR);
return false;
}
}
// Store addresses and format them to fit nicely into the GUI
recipient.address = addresses.join("
");
if (!recipient.authenticatedMerchant.isEmpty()) {
qDebug() << "PaymentServer::processPaymentRequest: Secure payment "
"request from "
<< recipient.authenticatedMerchant;
} else {
qDebug() << "PaymentServer::processPaymentRequest: Insecure payment "
"request to "
<< addresses.join(", ");
}
return true;
}
void PaymentServer::fetchRequest(const QUrl &url) {
QNetworkRequest netRequest;
netRequest.setAttribute(QNetworkRequest::User,
BIP70_MESSAGE_PAYMENTREQUEST);
netRequest.setUrl(url);
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
netManager->get(netRequest);
}
void PaymentServer::fetchPaymentACK(CWallet *wallet,
SendCoinsRecipient recipient,
QByteArray transaction) {
const payments::PaymentDetails &details =
recipient.paymentRequest.getDetails();
if (!details.has_payment_url()) {
return;
}
QNetworkRequest netRequest;
netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
netRequest.setUrl(QString::fromStdString(details.payment_url()));
netRequest.setHeader(QNetworkRequest::ContentTypeHeader,
BIP71_MIMETYPE_PAYMENT);
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK);
payments::Payment payment;
payment.set_merchant_data(details.merchant_data());
payment.add_transactions(transaction.data(), transaction.size());
// Create a new refund address, or re-use:
- QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant);
- std::string strAccount = account.toStdString();
- std::set refundAddresses =
- wallet->GetAccountAddresses(strAccount);
+ std::string label =
+ tr("Refund from %1").arg(recipient.authenticatedMerchant).toStdString();
+ std::set refundAddresses = wallet->GetLabelAddresses(label);
if (!refundAddresses.empty()) {
CScript s = GetScriptForDestination(*refundAddresses.begin());
payments::Output *refund_to = payment.add_refund_to();
refund_to->set_script(&s[0], s.size());
} else {
CPubKey newKey;
if (wallet->GetKeyFromPool(newKey)) {
CKeyID keyID = newKey.GetID();
- wallet->SetAddressBook(keyID, strAccount, "refund");
+ wallet->SetAddressBook(keyID, label, "refund");
CScript s = GetScriptForDestination(keyID);
payments::Output *refund_to = payment.add_refund_to();
refund_to->set_script(&s[0], s.size());
} else {
// This should never happen, because sending coins should have just
// unlocked the wallet and refilled the keypool.
qWarning() << "PaymentServer::fetchPaymentACK: Error getting "
"refund key, refund_to not set";
}
}
int length = payment.ByteSize();
netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);
QByteArray serData(length, '\0');
if (payment.SerializeToArray(serData.data(), length)) {
netManager->post(netRequest, serData);
} else {
// This should never happen, either.
qWarning() << "PaymentServer::fetchPaymentACK: Error serializing "
"payment message";
}
}
void PaymentServer::netRequestFinished(QNetworkReply *reply) {
reply->deleteLater();
// BIP70 DoS protection
if (!verifySize(reply->size())) {
Q_EMIT message(
tr("Payment request rejected"),
tr("Payment request %1 is too large (%2 bytes, allowed %3 bytes).")
.arg(reply->request().url().toString())
.arg(reply->size())
.arg(BIP70_MAX_PAYMENTREQUEST_SIZE),
CClientUIInterface::MSG_ERROR);
return;
}
if (reply->error() != QNetworkReply::NoError) {
QString msg = tr("Error communicating with %1: %2")
.arg(reply->request().url().toString())
.arg(reply->errorString());
qWarning() << "PaymentServer::netRequestFinished: " << msg;
Q_EMIT message(tr("Payment request error"), msg,
CClientUIInterface::MSG_ERROR);
return;
}
QByteArray data = reply->readAll();
QString requestType =
reply->request().attribute(QNetworkRequest::User).toString();
if (requestType == BIP70_MESSAGE_PAYMENTREQUEST) {
PaymentRequestPlus request;
SendCoinsRecipient recipient;
if (!request.parse(data)) {
qWarning() << "PaymentServer::netRequestFinished: Error parsing "
"payment request";
Q_EMIT message(tr("Payment request error"),
tr("Payment request cannot be parsed!"),
CClientUIInterface::MSG_ERROR);
} else if (processPaymentRequest(request, recipient)) {
Q_EMIT receivedPaymentRequest(recipient);
}
return;
} else if (requestType == BIP70_MESSAGE_PAYMENTACK) {
payments::PaymentACK paymentACK;
if (!paymentACK.ParseFromArray(data.data(), data.size())) {
QString msg = tr("Bad response from server %1")
.arg(reply->request().url().toString());
qWarning() << "PaymentServer::netRequestFinished: " << msg;
Q_EMIT message(tr("Payment request error"), msg,
CClientUIInterface::MSG_ERROR);
} else {
Q_EMIT receivedPaymentACK(GUIUtil::HtmlEscape(paymentACK.memo()));
}
}
}
void PaymentServer::reportSslErrors(QNetworkReply *reply,
const QList &errs) {
Q_UNUSED(reply);
QString errString;
for (const QSslError &err : errs) {
qWarning() << "PaymentServer::reportSslErrors: " << err;
errString += err.errorString() + "\n";
}
Q_EMIT message(tr("Network request error"), errString,
CClientUIInterface::MSG_ERROR);
}
void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) {
this->optionsModel = _optionsModel;
}
void PaymentServer::handlePaymentACK(const QString &paymentACKMsg) {
// currently we don't further process or store the paymentACK message
Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg,
CClientUIInterface::ICON_INFORMATION |
CClientUIInterface::MODAL);
}
bool PaymentServer::verifyNetwork(
const payments::PaymentDetails &requestDetails) {
bool fVerified = requestDetails.network() == Params().NetworkIDString();
if (!fVerified) {
qWarning() << QString("PaymentServer::%1: Payment request network "
"\"%2\" doesn't match client network \"%3\".")
.arg(__func__)
.arg(QString::fromStdString(requestDetails.network()))
.arg(QString::fromStdString(
Params().NetworkIDString()));
}
return fVerified;
}
bool PaymentServer::verifyExpired(
const payments::PaymentDetails &requestDetails) {
bool fVerified = (requestDetails.has_expires() &&
(int64_t)requestDetails.expires() < GetTime());
if (fVerified) {
const QString requestExpires = QString::fromStdString(DateTimeStrFormat(
"%Y-%m-%d %H:%M:%S", (int64_t)requestDetails.expires()));
qWarning() << QString(
"PaymentServer::%1: Payment request expired \"%2\".")
.arg(__func__)
.arg(requestExpires);
}
return fVerified;
}
bool PaymentServer::verifySize(qint64 requestSize) {
bool fVerified = (requestSize <= BIP70_MAX_PAYMENTREQUEST_SIZE);
if (!fVerified) {
qWarning() << QString("PaymentServer::%1: Payment request too large "
"(%2 bytes, allowed %3 bytes).")
.arg(__func__)
.arg(requestSize)
.arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
}
return fVerified;
}
bool PaymentServer::verifyAmount(const Amount requestAmount) {
bool fVerified = MoneyRange(Amount(requestAmount));
if (!fVerified) {
qWarning() << QString("PaymentServer::%1: Payment request amount out "
"of allowed range (%2, allowed 0 - %3).")
.arg(__func__)
.arg(requestAmount / SATOSHI)
.arg(MAX_MONEY / SATOSHI);
}
return fVerified;
}
X509_STORE *PaymentServer::getCertStore() {
return certStore.get();
}
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index b60843c4c..3319b9356 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -1,218 +1,222 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "rpc/client.h"
#include "rpc/protocol.h"
#include "util.h"
#include
#include
#include
class CRPCConvertParam {
public:
std::string methodName; //!< method whose params want conversion
int paramIdx; //!< 0-based idx of param to convert
std::string paramName; //!< parameter name
};
/**
* Specifiy a (method, idx, name) here if the argument is a non-string RPC
* argument and needs to be converted from JSON.
*
* @note Parameter indexes start from 0.
*/
static const CRPCConvertParam vRPCConvertParams[] = {
{"setmocktime", 0, "timestamp"},
{"generate", 0, "nblocks"},
{"generate", 1, "maxtries"},
{"generatetoaddress", 0, "nblocks"},
{"generatetoaddress", 2, "maxtries"},
{"getnetworkhashps", 0, "nblocks"},
{"getnetworkhashps", 1, "height"},
{"sendtoaddress", 1, "amount"},
{"sendtoaddress", 4, "subtractfeefromamount"},
{"settxfee", 0, "amount"},
{"getreceivedbyaddress", 1, "minconf"},
{"getreceivedbyaccount", 1, "minconf"},
+ {"getreceivedbylabel", 1, "minconf"},
{"listreceivedbyaddress", 0, "minconf"},
{"listreceivedbyaddress", 1, "include_empty"},
{"listreceivedbyaddress", 2, "include_watchonly"},
+ {"listreceivedbyaddress", 3, "address_filter"},
{"listreceivedbyaccount", 0, "minconf"},
{"listreceivedbyaccount", 1, "include_empty"},
{"listreceivedbyaccount", 2, "include_watchonly"},
- {"listreceivedbyaddress", 3, "address_filter"},
+ {"listreceivedbylabel", 0, "minconf"},
+ {"listreceivedbylabel", 1, "include_empty"},
+ {"listreceivedbylabel", 2, "include_watchonly"},
{"getbalance", 1, "minconf"},
{"getbalance", 2, "include_watchonly"},
{"getblockhash", 0, "height"},
{"waitforblockheight", 0, "height"},
{"waitforblockheight", 1, "timeout"},
{"waitforblock", 1, "timeout"},
{"waitfornewblock", 0, "timeout"},
{"move", 2, "amount"},
{"move", 3, "minconf"},
{"sendfrom", 2, "amount"},
{"sendfrom", 3, "minconf"},
{"listtransactions", 1, "count"},
{"listtransactions", 2, "skip"},
{"listtransactions", 3, "include_watchonly"},
{"listaccounts", 0, "minconf"},
{"listaccounts", 1, "include_watchonly"},
{"walletpassphrase", 1, "timeout"},
{"getblocktemplate", 0, "template_request"},
{"listsinceblock", 1, "target_confirmations"},
{"listsinceblock", 2, "include_watchonly"},
{"sendmany", 1, "amounts"},
{"sendmany", 2, "minconf"},
{"sendmany", 4, "subtractfeefrom"},
{"addmultisigaddress", 0, "nrequired"},
{"addmultisigaddress", 1, "keys"},
{"createmultisig", 0, "nrequired"},
{"createmultisig", 1, "keys"},
{"listunspent", 0, "minconf"},
{"listunspent", 1, "maxconf"},
{"listunspent", 2, "addresses"},
{"listunspent", 4, "query_options"},
{"getblock", 1, "verbosity"},
{"getblockheader", 1, "verbose"},
{"getchaintxstats", 0, "nblocks"},
{"gettransaction", 1, "include_watchonly"},
{"getrawtransaction", 1, "verbose"},
{"createrawtransaction", 0, "inputs"},
{"createrawtransaction", 1, "outputs"},
{"createrawtransaction", 2, "locktime"},
{"signrawtransaction", 1, "prevtxs"},
{"signrawtransaction", 2, "privkeys"},
{"sendrawtransaction", 1, "allowhighfees"},
{"combinerawtransaction", 0, "txs"},
{"fundrawtransaction", 1, "options"},
{"gettxout", 1, "n"},
{"gettxout", 2, "include_mempool"},
{"gettxoutproof", 0, "txids"},
{"lockunspent", 0, "unlock"},
{"lockunspent", 1, "transactions"},
{"importprivkey", 2, "rescan"},
{"importaddress", 2, "rescan"},
{"importaddress", 3, "p2sh"},
{"importpubkey", 2, "rescan"},
{"importmulti", 0, "requests"},
{"importmulti", 1, "options"},
{"verifychain", 0, "checklevel"},
{"verifychain", 1, "nblocks"},
{"pruneblockchain", 0, "height"},
{"keypoolrefill", 0, "newsize"},
{"getrawmempool", 0, "verbose"},
{"estimatefee", 0, "nblocks"},
{"prioritisetransaction", 1, "priority_delta"},
{"prioritisetransaction", 2, "fee_delta"},
{"setban", 2, "bantime"},
{"setban", 3, "absolute"},
{"setnetworkactive", 0, "state"},
{"getmempoolancestors", 1, "verbose"},
{"getmempooldescendants", 1, "verbose"},
{"disconnectnode", 1, "nodeid"},
// Echo with conversion (For testing only)
{"echojson", 0, "arg0"},
{"echojson", 1, "arg1"},
{"echojson", 2, "arg2"},
{"echojson", 3, "arg3"},
{"echojson", 4, "arg4"},
{"echojson", 5, "arg5"},
{"echojson", 6, "arg6"},
{"echojson", 7, "arg7"},
{"echojson", 8, "arg8"},
{"echojson", 9, "arg9"},
{"rescanblockchain", 0, "start_height"},
{"rescanblockchain", 1, "stop_height"},
};
class CRPCConvertTable {
private:
std::set> members;
std::set> membersByName;
public:
CRPCConvertTable();
bool convert(const std::string &method, int idx) {
return (members.count(std::make_pair(method, idx)) > 0);
}
bool convert(const std::string &method, const std::string &name) {
return (membersByName.count(std::make_pair(method, name)) > 0);
}
};
CRPCConvertTable::CRPCConvertTable() {
const unsigned int n_elem =
(sizeof(vRPCConvertParams) / sizeof(vRPCConvertParams[0]));
for (unsigned int i = 0; i < n_elem; i++) {
members.insert(std::make_pair(vRPCConvertParams[i].methodName,
vRPCConvertParams[i].paramIdx));
membersByName.insert(std::make_pair(vRPCConvertParams[i].methodName,
vRPCConvertParams[i].paramName));
}
}
static CRPCConvertTable rpcCvtTable;
/**
* Non-RFC4627 JSON parser, accepts internal values (such as numbers, true,
* false, null) as well as objects and arrays.
*/
UniValue ParseNonRFCJSONValue(const std::string &strVal) {
UniValue jVal;
if (!jVal.read(std::string("[") + strVal + std::string("]")) ||
!jVal.isArray() || jVal.size() != 1)
throw std::runtime_error(std::string("Error parsing JSON:") + strVal);
return jVal[0];
}
UniValue RPCConvertValues(const std::string &strMethod,
const std::vector &strParams) {
UniValue params(UniValue::VARR);
for (unsigned int idx = 0; idx < strParams.size(); idx++) {
const std::string &strVal = strParams[idx];
if (!rpcCvtTable.convert(strMethod, idx)) {
// insert string value directly
params.push_back(strVal);
} else {
// parse string as JSON, insert bool/number/object/etc. value
params.push_back(ParseNonRFCJSONValue(strVal));
}
}
return params;
}
UniValue RPCConvertNamedValues(const std::string &strMethod,
const std::vector &strParams) {
UniValue params(UniValue::VOBJ);
for (const std::string &s : strParams) {
size_t pos = s.find("=");
if (pos == std::string::npos) {
throw(std::runtime_error("No '=' in named argument '" + s +
"', this needs to be present for every "
"argument (even if it is empty)"));
}
std::string name = s.substr(0, pos);
std::string value = s.substr(pos + 1);
if (!rpcCvtTable.convert(strMethod, name)) {
// insert string value directly
params.pushKV(name, value);
} else {
// parse string as JSON, insert bool/number/object/etc. value
params.pushKV(name, ParseNonRFCJSONValue(value));
}
}
return params;
}
diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h
index d5d3185a8..21bcce942 100644
--- a/src/rpc/protocol.h
+++ b/src/rpc/protocol.h
@@ -1,137 +1,139 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_RPCPROTOCOL_H
#define BITCOIN_RPCPROTOCOL_H
#include "fs.h"
#include
#include
#include