Dash Core Source Documentation (0.16.0.1)

Find detailed information regarding the Dash Core source code.

paymentserver.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2015 The Bitcoin Core developers
2 // Copyright (c) 2014-2020 The Dash Core developers
3 // Distributed under the MIT software license, see the accompanying
4 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 
6 #include <qt/paymentserver.h>
7 
8 #include <qt/bitcoinunits.h>
9 #include <qt/guiutil.h>
10 #include <qt/optionsmodel.h>
11 
12 #include <base58.h>
13 #include <chainparams.h>
14 #include <policy/policy.h>
15 #include <ui_interface.h>
16 #include <util.h>
17 #include <wallet/wallet.h>
18 
19 #include <cstdlib>
20 #include <memory>
21 
22 #include <openssl/x509_vfy.h>
23 
24 #include <QApplication>
25 #include <QByteArray>
26 #include <QDataStream>
27 #include <QDateTime>
28 #include <QDebug>
29 #include <QFile>
30 #include <QFileOpenEvent>
31 #include <QHash>
32 #include <QList>
33 #include <QLocalServer>
34 #include <QLocalSocket>
35 #include <QNetworkAccessManager>
36 #include <QNetworkProxy>
37 #include <QNetworkReply>
38 #include <QNetworkRequest>
39 #include <QSslCertificate>
40 #include <QSslError>
41 #include <QSslSocket>
42 #include <QStringList>
43 #include <QTextDocument>
44 
45 #if QT_VERSION < 0x050000
46 #include <QUrl>
47 #else
48 #include <QUrlQuery>
49 #endif
50 
51 const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
52 const QString BITCOIN_IPC_PREFIX("dash:");
53 // BIP70 payment protocol messages
54 const char* BIP70_MESSAGE_PAYMENTACK = "PaymentACK";
55 const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest";
56 // BIP71 payment protocol media types
57 const char* BIP71_MIMETYPE_PAYMENT = "application/dash-payment";
58 const char* BIP71_MIMETYPE_PAYMENTACK = "application/dash-paymentack";
59 const char* BIP71_MIMETYPE_PAYMENTREQUEST = "application/dash-paymentrequest";
60 
62  void operator()(X509_STORE* b) {
63  X509_STORE_free(b);
64  }
65 };
66 
67 struct X509Deleter {
68  void operator()(X509* b) { X509_free(b); }
69 };
70 
71 namespace // Anon namespace
72 {
73  std::unique_ptr<X509_STORE, X509StoreDeleter> certStore;
74 }
75 
76 //
77 // Create a name that is unique for:
78 // testnet / non-testnet
79 // data directory
80 //
81 static QString ipcServerName()
82 {
83  QString name("DashQt");
84 
85  // Append a simple hash of the datadir
86  // Note that GetDataDir(true) returns a different path
87  // for -testnet versus main net
88  QString ddir(GUIUtil::boostPathToQString(GetDataDir(true)));
89  name.append(QString::number(qHash(ddir)));
90 
91  return name;
92 }
93 
94 //
95 // We store payment URIs and requests received before
96 // the main GUI window is up and ready to ask the user
97 // to send payment.
98 
99 static QList<QString> savedPaymentRequests;
100 
101 static void ReportInvalidCertificate(const QSslCertificate& cert)
102 {
103 #if QT_VERSION < 0x050000
104  qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
105 #else
106  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);
107 #endif
108 }
109 
110 //
111 // Load OpenSSL's list of root certificate authorities
112 //
113 void PaymentServer::LoadRootCAs(X509_STORE* _store)
114 {
115  // Unit tests mostly use this, to pass in fake root CAs:
116  if (_store)
117  {
118  certStore.reset(_store);
119  return;
120  }
121 
122  // Normal execution, use either -rootcertificates or system certs:
123  certStore.reset(X509_STORE_new());
124 
125  // Note: use "-system-" default here so that users can pass -rootcertificates=""
126  // and get 'I don't like X.509 certificates, don't trust anybody' behavior:
127  QString certFile = QString::fromStdString(gArgs.GetArg("-rootcertificates", "-system-"));
128 
129  // Empty store
130  if (certFile.isEmpty()) {
131  qDebug() << QString("PaymentServer::%1: Payment request authentication via X.509 certificates disabled.").arg(__func__);
132  return;
133  }
134 
135  QList<QSslCertificate> certList;
136 
137  if (certFile != "-system-") {
138  qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root certificate.").arg(__func__).arg(certFile);
139 
140  certList = QSslCertificate::fromPath(certFile);
141  // Use those certificates when fetching payment requests, too:
142  QSslSocket::setDefaultCaCertificates(certList);
143  } else
144  certList = QSslSocket::systemCaCertificates();
145 
146  int nRootCerts = 0;
147  const QDateTime currentTime = QDateTime::currentDateTime();
148 
149  for (const QSslCertificate& cert : certList) {
150  // Don't log NULL certificates
151  if (cert.isNull())
152  continue;
153 
154  // Not yet active/valid, or expired certificate
155  if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) {
157  continue;
158  }
159 
160 #if QT_VERSION >= 0x050000
161  // Blacklisted certificate
162  if (cert.isBlacklisted()) {
164  continue;
165  }
166 #endif
167  QByteArray certData = cert.toDer();
168  const unsigned char *data = (const unsigned char *)certData.data();
169 
170  std::unique_ptr<X509, X509Deleter> x509(d2i_X509(0, &data, certData.size()));
171  if (x509 && X509_STORE_add_cert(certStore.get(), x509.get()))
172  {
173  // Note: X509_STORE increases the reference count to the X509 object,
174  // we still have to release our reference to it.
175  ++nRootCerts;
176  }
177  else
178  {
180  continue;
181  }
182  }
183  qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts << " root certificates";
184 
185  // Project for another day:
186  // Fetch certificate revocation lists, and add them to certStore.
187  // Issues to consider:
188  // performance (start a thread to fetch in background?)
189  // privacy (fetch through tor/proxy so IP address isn't revealed)
190  // would it be easier to just use a compiled-in blacklist?
191  // or use Qt's blacklist?
192  // "certificate stapling" with server-side caching is more efficient
193 }
194 
195 //
196 // Sending to the server is done synchronously, at startup.
197 // If the server isn't already running, startup continues,
198 // and the items in savedPaymentRequest will be handled
199 // when uiReady() is called.
200 //
201 // Warning: ipcSendCommandLine() is called early in init,
202 // so don't use "Q_EMIT message()", but "QMessageBox::"!
203 //
204 void PaymentServer::ipcParseCommandLine(int argc, char* argv[])
205 {
206  for (int i = 1; i < argc; i++)
207  {
208  QString arg(argv[i]);
209  if (arg.startsWith("-"))
210  continue;
211 
212  // If the dash: URI contains a payment request, we are not able to detect the
213  // network as that would require fetching and parsing the payment request.
214  // That means clicking such an URI which contains a testnet payment request
215  // will start a mainnet instance and throw a "wrong network" error.
216  if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // dash: URI
217  {
218  savedPaymentRequests.append(arg);
219 
221  if (GUIUtil::parseBitcoinURI(arg, &r) && !r.address.isEmpty())
222  {
223  auto tempChainParams = CreateChainParams(CBaseChainParams::MAIN);
224 
225  if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
227  } else {
228  tempChainParams = CreateChainParams(CBaseChainParams::TESTNET);
229  if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
231  }
232  }
233  }
234  }
235  else if (QFile::exists(arg)) // Filename
236  {
237  savedPaymentRequests.append(arg);
238 
239  PaymentRequestPlus request;
240  if (readPaymentRequestFromFile(arg, request))
241  {
242  if (request.getDetails().network() == "main")
243  {
245  }
246  else if (request.getDetails().network() == "test")
247  {
249  }
250  }
251  }
252  else
253  {
254  // Printing to debug.log is about the best we can do here, the
255  // GUI hasn't started yet so we can't pop up a message box.
256  qWarning() << "PaymentServer::ipcSendCommandLine: Payment request file does not exist: " << arg;
257  }
258  }
259 }
260 
261 //
262 // Sending to the server is done synchronously, at startup.
263 // If the server isn't already running, startup continues,
264 // and the items in savedPaymentRequest will be handled
265 // when uiReady() is called.
266 //
268 {
269  bool fResult = false;
270  for (const QString& r : savedPaymentRequests)
271  {
272  QLocalSocket* socket = new QLocalSocket();
273  socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
274  if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT))
275  {
276  delete socket;
277  socket = nullptr;
278  return false;
279  }
280 
281  QByteArray block;
282  QDataStream out(&block, QIODevice::WriteOnly);
283  out.setVersion(QDataStream::Qt_4_0);
284  out << r;
285  out.device()->seek(0);
286 
287  socket->write(block);
288  socket->flush();
289  socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
290  socket->disconnectFromServer();
291 
292  delete socket;
293  socket = nullptr;
294  fResult = true;
295  }
296 
297  return fResult;
298 }
299 
300 PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) :
301  QObject(parent),
302  saveURIs(true),
303  uriServer(0),
304  netManager(0),
305  optionsModel(0)
306 {
307  // Verify that the version of the library that we linked against is
308  // compatible with the version of the headers we compiled against.
309  GOOGLE_PROTOBUF_VERIFY_VERSION;
310 
311  // Install global event filter to catch QFileOpenEvents
312  // on Mac: sent when you click dash: links
313  // other OSes: helpful when dealing with payment request files
314  if (parent)
315  parent->installEventFilter(this);
316 
317  QString name = ipcServerName();
318 
319  // Clean up old socket leftover from a crash:
320  QLocalServer::removeServer(name);
321 
322  if (startLocalServer)
323  {
324  uriServer = new QLocalServer(this);
325 
326  if (!uriServer->listen(name)) {
327  // constructor is called early in init, so don't use "Q_EMIT message()" here
328  QMessageBox::critical(0, tr("Payment request error"),
329  tr("Cannot start dash: click-to-pay handler"));
330  }
331  else {
332  connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection()));
333  connect(this, SIGNAL(receivedPaymentACK(QString)), this, SLOT(handlePaymentACK(QString)));
334  }
335  }
336 }
337 
339 {
340  google::protobuf::ShutdownProtobufLibrary();
341 }
342 
343 //
344 // OSX-specific way of handling dash: URIs and PaymentRequest mime types.
345 // Also used by paymentservertests.cpp and when opening a payment request file
346 // via "Open URI..." menu entry.
347 //
348 bool PaymentServer::eventFilter(QObject *object, QEvent *event)
349 {
350  if (event->type() == QEvent::FileOpen) {
351  QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent*>(event);
352  if (!fileEvent->file().isEmpty())
353  handleURIOrFile(fileEvent->file());
354  else if (!fileEvent->url().isEmpty())
355  handleURIOrFile(fileEvent->url().toString());
356 
357  return true;
358  }
359 
360  return QObject::eventFilter(object, event);
361 }
362 
364 {
365  if (!optionsModel)
366  return;
367  delete netManager;
368 
369  // netManager is used to fetch paymentrequests given in dash: URIs
370  netManager = new QNetworkAccessManager(this);
371 
372  QNetworkProxy proxy;
373 
374  // Query active SOCKS5 proxy
375  if (optionsModel->getProxySettings(proxy)) {
376  netManager->setProxy(proxy);
377 
378  qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port();
379  }
380  else
381  qDebug() << "PaymentServer::initNetManager: No active proxy server found.";
382 
383  connect(netManager, SIGNAL(finished(QNetworkReply*)),
384  this, SLOT(netRequestFinished(QNetworkReply*)));
385  connect(netManager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> &)),
386  this, SLOT(reportSslErrors(QNetworkReply*, const QList<QSslError> &)));
387 }
388 
390 {
391  initNetManager();
392 
393  saveURIs = false;
394  for (const QString& s : savedPaymentRequests)
395  {
396  handleURIOrFile(s);
397  }
398  savedPaymentRequests.clear();
399 }
400 
401 void PaymentServer::handleURIOrFile(const QString& s)
402 {
403  if (saveURIs)
404  {
405  savedPaymentRequests.append(s);
406  return;
407  }
408 
409  if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // dash: URI
410  {
411 #if QT_VERSION < 0x050000
412  QUrl uri(s);
413 #else
414  QUrlQuery uri((QUrl(s)));
415 #endif
416  if (uri.hasQueryItem("r")) // payment request URI
417  {
418  QByteArray temp;
419  temp.append(uri.queryItemValue("r"));
420  QString decoded = QUrl::fromPercentEncoding(temp);
421  QUrl fetchUrl(decoded, QUrl::StrictMode);
422 
423  if (fetchUrl.isValid())
424  {
425  qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" << fetchUrl << ")";
426  fetchRequest(fetchUrl);
427  }
428  else
429  {
430  qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " << fetchUrl;
431  Q_EMIT message(tr("URI handling"),
432  tr("Payment request fetch URL is invalid: %1").arg(fetchUrl.toString()),
434  }
435 
436  return;
437  }
438  else // normal URI
439  {
440  SendCoinsRecipient recipient;
441  if (GUIUtil::parseBitcoinURI(s, &recipient))
442  {
443  if (!IsValidDestinationString(recipient.address.toStdString())) {
444  Q_EMIT message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address),
446  }
447  else
448  Q_EMIT receivedPaymentRequest(recipient);
449  }
450  else
451  Q_EMIT message(tr("URI handling"),
452  tr("URI cannot be parsed! This can be caused by an invalid Dash address or malformed URI parameters."),
454 
455  return;
456  }
457  }
458 
459  if (QFile::exists(s)) // payment request file
460  {
461  PaymentRequestPlus request;
462  SendCoinsRecipient recipient;
463  if (!readPaymentRequestFromFile(s, request))
464  {
465  Q_EMIT message(tr("Payment request file handling"),
466  tr("Payment request file cannot be read! This can be caused by an invalid payment request file."),
468  }
469  else if (processPaymentRequest(request, recipient))
470  Q_EMIT receivedPaymentRequest(recipient);
471 
472  return;
473  }
474 }
475 
477 {
478  QLocalSocket *clientConnection = uriServer->nextPendingConnection();
479 
480  while (clientConnection->bytesAvailable() < (int)sizeof(quint32))
481  clientConnection->waitForReadyRead();
482 
483  connect(clientConnection, SIGNAL(disconnected()),
484  clientConnection, SLOT(deleteLater()));
485 
486  QDataStream in(clientConnection);
487  in.setVersion(QDataStream::Qt_4_0);
488  if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
489  return;
490  }
491  QString msg;
492  in >> msg;
493 
494  handleURIOrFile(msg);
495 }
496 
497 //
498 // Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine()
499 // so don't use "Q_EMIT message()", but "QMessageBox::"!
500 //
501 bool PaymentServer::readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request)
502 {
503  QFile f(filename);
504  if (!f.open(QIODevice::ReadOnly)) {
505  qWarning() << QString("PaymentServer::%1: Failed to open %2").arg(__func__).arg(filename);
506  return false;
507  }
508 
509  // BIP70 DoS protection
510  if (!verifySize(f.size())) {
511  return false;
512  }
513 
514  QByteArray data = f.readAll();
515 
516  return request.parse(data);
517 }
518 
520 {
521  if (!optionsModel)
522  return false;
523 
524  if (request.IsInitialized()) {
525  // Payment request network matches client network?
526  if (!verifyNetwork(request.getDetails())) {
527  Q_EMIT message(tr("Payment request rejected"), tr("Payment request network doesn't match client network."),
529 
530  return false;
531  }
532 
533  // Make sure any payment requests involved are still valid.
534  // This is re-checked just before sending coins in WalletModel::sendCoins().
535  if (verifyExpired(request.getDetails())) {
536  Q_EMIT message(tr("Payment request rejected"), tr("Payment request expired."),
538 
539  return false;
540  }
541  } else {
542  Q_EMIT message(tr("Payment request error"), tr("Payment request is not initialized."),
544 
545  return false;
546  }
547 
548  recipient.paymentRequest = request;
549  recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo());
550 
551  request.getMerchant(certStore.get(), recipient.authenticatedMerchant);
552 
553  QList<std::pair<CScript, CAmount> > sendingTos = request.getPayTo();
554  QStringList addresses;
555 
556  for (const std::pair<CScript, CAmount>& sendingTo : sendingTos) {
557  // Extract and check destination addresses
558  CTxDestination dest;
559  if (ExtractDestination(sendingTo.first, dest)) {
560  // Append destination address
561  addresses.append(QString::fromStdString(EncodeDestination(dest)));
562  }
563  else if (!recipient.authenticatedMerchant.isEmpty()) {
564  // Unauthenticated payment requests to custom dash addresses are not supported
565  // (there is no good way to tell the user where they are paying in a way they'd
566  // have a chance of understanding).
567  Q_EMIT message(tr("Payment request rejected"),
568  tr("Unverified payment requests to custom payment scripts are unsupported."),
570  return false;
571  }
572 
573  // Dash amounts are stored as (optional) uint64 in the protobuf messages (see paymentrequest.proto),
574  // but CAmount is defined as int64_t. Because of that we need to verify that amounts are in a valid range
575  // and no overflow has happened.
576  if (!verifyAmount(sendingTo.second)) {
577  Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR);
578  return false;
579  }
580 
581  // Extract and check amounts
582  CTxOut txOut(sendingTo.second, sendingTo.first);
583  if (IsDust(txOut, ::dustRelayFee)) {
584  Q_EMIT message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).")
585  .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)),
587 
588  return false;
589  }
590 
591  recipient.amount += sendingTo.second;
592  // Also verify that the final amount is still in a valid range after adding additional amounts.
593  if (!verifyAmount(recipient.amount)) {
594  Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR);
595  return false;
596  }
597  }
598  // Store addresses and format them to fit nicely into the GUI
599  recipient.address = addresses.join("<br />");
600 
601  if (!recipient.authenticatedMerchant.isEmpty()) {
602  qDebug() << "PaymentServer::processPaymentRequest: Secure payment request from " << recipient.authenticatedMerchant;
603  }
604  else {
605  qDebug() << "PaymentServer::processPaymentRequest: Insecure payment request to " << addresses.join(", ");
606  }
607 
608  return true;
609 }
610 
611 void PaymentServer::fetchRequest(const QUrl& url)
612 {
613  QNetworkRequest netRequest;
614  netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTREQUEST);
615  netRequest.setUrl(url);
616  netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
617  netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
618  netManager->get(netRequest);
619 }
620 
621 void PaymentServer::fetchPaymentACK(CWallet* wallet, const SendCoinsRecipient& recipient, QByteArray transaction)
622 {
623  const payments::PaymentDetails& details = recipient.paymentRequest.getDetails();
624  if (!details.has_payment_url())
625  return;
626 
627  QNetworkRequest netRequest;
628  netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
629  netRequest.setUrl(QString::fromStdString(details.payment_url()));
630  netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BIP71_MIMETYPE_PAYMENT);
631  netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
632  netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK);
633 
634  payments::Payment payment;
635  payment.set_merchant_data(details.merchant_data());
636  payment.add_transactions(transaction.data(), transaction.size());
637 
638  // Create a new refund address, or re-use:
639  QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant);
640  std::string strAccount = account.toStdString();
641  std::set<CTxDestination> refundAddresses = wallet->GetAccountAddresses(strAccount);
642  if (!refundAddresses.empty()) {
643  CScript s = GetScriptForDestination(*refundAddresses.begin());
644  payments::Output* refund_to = payment.add_refund_to();
645  refund_to->set_script(&s[0], s.size());
646  }
647  else {
648  CPubKey newKey;
649  if (wallet->GetKeyFromPool(newKey, false)) {
650  CKeyID keyID = newKey.GetID();
651  wallet->SetAddressBook(keyID, strAccount, "refund");
652 
653  CScript s = GetScriptForDestination(keyID);
654  payments::Output* refund_to = payment.add_refund_to();
655  refund_to->set_script(&s[0], s.size());
656  }
657  else {
658  // This should never happen, because sending coins should have
659  // just unlocked the wallet and refilled the keypool.
660  qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set";
661  }
662  }
663 
664  int length = payment.ByteSize();
665  netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);
666  QByteArray serData(length, '\0');
667  if (payment.SerializeToArray(serData.data(), length)) {
668  netManager->post(netRequest, serData);
669  }
670  else {
671  // This should never happen, either.
672  qWarning() << "PaymentServer::fetchPaymentACK: Error serializing payment message";
673  }
674 }
675 
676 void PaymentServer::netRequestFinished(QNetworkReply* reply)
677 {
678  reply->deleteLater();
679 
680  // BIP70 DoS protection
681  if (!verifySize(reply->size())) {
682  Q_EMIT message(tr("Payment request rejected"),
683  tr("Payment request %1 is too large (%2 bytes, allowed %3 bytes).")
684  .arg(reply->request().url().toString())
685  .arg(reply->size())
688  return;
689  }
690 
691  if (reply->error() != QNetworkReply::NoError) {
692  QString msg = tr("Error communicating with %1: %2")
693  .arg(reply->request().url().toString())
694  .arg(reply->errorString());
695 
696  qWarning() << "PaymentServer::netRequestFinished: " << msg;
697  Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
698  return;
699  }
700 
701  QByteArray data = reply->readAll();
702 
703  QString requestType = reply->request().attribute(QNetworkRequest::User).toString();
704  if (requestType == BIP70_MESSAGE_PAYMENTREQUEST)
705  {
706  PaymentRequestPlus request;
707  SendCoinsRecipient recipient;
708  if (!request.parse(data))
709  {
710  qWarning() << "PaymentServer::netRequestFinished: Error parsing payment request";
711  Q_EMIT message(tr("Payment request error"),
712  tr("Payment request cannot be parsed!"),
714  }
715  else if (processPaymentRequest(request, recipient))
716  Q_EMIT receivedPaymentRequest(recipient);
717 
718  return;
719  }
720  else if (requestType == BIP70_MESSAGE_PAYMENTACK)
721  {
722  payments::PaymentACK paymentACK;
723  if (!paymentACK.ParseFromArray(data.data(), data.size()))
724  {
725  QString msg = tr("Bad response from server %1")
726  .arg(reply->request().url().toString());
727 
728  qWarning() << "PaymentServer::netRequestFinished: " << msg;
729  Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
730  }
731  else
732  {
733  Q_EMIT receivedPaymentACK(GUIUtil::HtmlEscape(paymentACK.memo()));
734  }
735  }
736 }
737 
738 void PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError> &errs)
739 {
740  Q_UNUSED(reply);
741 
742  QString errString;
743  for (const QSslError& err : errs) {
744  qWarning() << "PaymentServer::reportSslErrors: " << err;
745  errString += err.errorString() + "\n";
746  }
747  Q_EMIT message(tr("Network request error"), errString, CClientUIInterface::MSG_ERROR);
748 }
749 
751 {
752  this->optionsModel = _optionsModel;
753 }
754 
755 void PaymentServer::handlePaymentACK(const QString& paymentACKMsg)
756 {
757  // currently we don't further process or store the paymentACK message
758  Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg, CClientUIInterface::ICON_INFORMATION | CClientUIInterface::MODAL);
759 }
760 
761 bool PaymentServer::verifyNetwork(const payments::PaymentDetails& requestDetails)
762 {
763  bool fVerified = requestDetails.network() == Params().NetworkIDString();
764  if (!fVerified) {
765  qWarning() << QString("PaymentServer::%1: Payment request network \"%2\" doesn't match client network \"%3\".")
766  .arg(__func__)
767  .arg(QString::fromStdString(requestDetails.network()))
768  .arg(QString::fromStdString(Params().NetworkIDString()));
769  }
770  return fVerified;
771 }
772 
773 bool PaymentServer::verifyExpired(const payments::PaymentDetails& requestDetails)
774 {
775  bool fVerified = (requestDetails.has_expires() && (int64_t)requestDetails.expires() < GetTime());
776  if (fVerified) {
777  const QString requestExpires = QString::fromStdString(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", (int64_t)requestDetails.expires()));
778  qWarning() << QString("PaymentServer::%1: Payment request expired \"%2\".")
779  .arg(__func__)
780  .arg(requestExpires);
781  }
782  return fVerified;
783 }
784 
785 bool PaymentServer::verifySize(qint64 requestSize)
786 {
787  bool fVerified = (requestSize <= BIP70_MAX_PAYMENTREQUEST_SIZE);
788  if (!fVerified) {
789  qWarning() << QString("PaymentServer::%1: Payment request too large (%2 bytes, allowed %3 bytes).")
790  .arg(__func__)
791  .arg(requestSize)
793  }
794  return fVerified;
795 }
796 
797 bool PaymentServer::verifyAmount(const CAmount& requestAmount)
798 {
799  bool fVerified = MoneyRange(requestAmount);
800  if (!fVerified) {
801  qWarning() << QString("PaymentServer::%1: Payment request amount out of allowed range (%2, allowed 0 - %3).")
802  .arg(__func__)
803  .arg(requestAmount)
804  .arg(MAX_MONEY);
805  }
806  return fVerified;
807 }
808 
810 {
811  return certStore.get();
812 }
static void LoadRootCAs(X509_STORE *store=nullptr)
std::string NetworkIDString() const
Return the BIP70 network string (main, test or regtest)
Definition: chainparams.h:76
static bool verifyAmount(const CAmount &requestAmount)
boost::variant< CNoDestination, CKeyID, CScriptID > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:80
bool ExtractDestination(const CScript &scriptPubKey, CTxDestination &addressRet)
Parse a standard scriptPubKey for the destination address.
Definition: standard.cpp:158
PaymentRequestPlus paymentRequest
Definition: walletmodel.h:58
const char * BIP71_MIMETYPE_PAYMENT
void message(const QString &title, const QString &message, unsigned int style)
static bool verifyNetwork(const payments::PaymentDetails &requestDetails)
void operator()(X509_STORE *b)
bool getProxySettings(QNetworkProxy &proxy) const
void setOptionsModel(OptionsModel *optionsModel)
static const CAmount MAX_MONEY
No amount larger than this (in satoshi) is valid.
Definition: amount.h:26
bool processPaymentRequest(const PaymentRequestPlus &request, SendCoinsRecipient &recipient)
bool eventFilter(QObject *object, QEvent *event)
void handlePaymentACK(const QString &paymentACKMsg)
std::string DateTimeStrFormat(const char *pszFormat, int64_t nTime)
Definition: utiltime.cpp:93
void receivedPaymentACK(const QString &paymentACKMsg)
bool MoneyRange(const CAmount &nValue)
Definition: amount.h:27
void operator()(X509 *b)
void receivedPaymentRequest(SendCoinsRecipient)
false true true true
Definition: bls_dkg.cpp:176
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:481
bool GetKeyFromPool(CPubKey &key, bool fInternal)
Definition: wallet.cpp:4521
bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out)
Definition: guiutil.cpp:358
static void ipcParseCommandLine(int argc, char *argv[])
CKeyID GetID() const
Get the KeyID of this public key (hash of its serialization)
Definition: pubkey.h:149
QLocalServer * uriServer
static QT_END_NAMESPACE const qint64 BIP70_MAX_PAYMENTREQUEST_SIZE
Definition: paymentserver.h:56
int getDisplayUnit() const
Definition: optionsmodel.h:82
void handleURIOrFile(const QString &s)
Force blocking, modal message box dialog (not just OS notification)
Definition: ui_interface.h:64
static const std::string MAIN
BIP70 chain name strings (main, test or regtest)
static void ReportInvalidCertificate(const QSslCertificate &cert)
static bool ipcSendCommandLine()
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
bool SetAddressBook(const CTxDestination &address, const std::string &strName, const std::string &purpose)
Definition: wallet.cpp:4277
int64_t GetTime()
Return system time (or mocked time, if set)
Definition: utiltime.cpp:22
bool IsValidDestinationString(const std::string &str, const CChainParams &params)
Definition: base58.cpp:341
const char * BIP71_MIMETYPE_PAYMENTACK
void netRequestFinished(QNetworkReply *)
const int BITCOIN_IPC_CONNECT_TIMEOUT
bool getMerchant(X509_STORE *certStore, QString &merchant) const
std::unique_ptr< CChainParams > CreateChainParams(const std::string &chain)
Creates and returns a std::unique_ptr<CChainParams> of the chosen chain.
static bool readPaymentRequestFromFile(const QString &filename, PaymentRequestPlus &request)
const char * name
Definition: rest.cpp:36
void fetchRequest(const QUrl &url)
void SelectParams(const std::string &network)
Sets the params returned by Params() to those for the given BIP70 chain name.
const char * BIP70_MESSAGE_PAYMENTREQUEST
An encapsulated public key.
Definition: pubkey.h:30
const payments::PaymentDetails & getDetails() const
static bool verifyExpired(const payments::PaymentDetails &requestDetails)
QList< std::pair< CScript, CAmount > > getPayTo() const
An output of a transaction.
Definition: transaction.h:144
PaymentServer(QObject *parent, bool startLocalServer=true)
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
Definition: standard.cpp:256
static X509_STORE * getCertStore()
QNetworkAccessManager * netManager
const std::string CLIENT_NAME
static QList< QString > savedPaymentRequests
bool parse(const QByteArray &data)
void fetchPaymentACK(CWallet *wallet, const SendCoinsRecipient &recipient, QByteArray transaction)
const char * BIP71_MIMETYPE_PAYMENTREQUEST
const char * BIP70_MESSAGE_PAYMENTACK
void reportSslErrors(QNetworkReply *, const QList< QSslError > &)
std::set< CTxDestination > GetAccountAddresses(const std::string &strAccount) const
Definition: wallet.cpp:4707
ArgsManager gArgs
Definition: util.cpp:108
std::string EncodeDestination(const CTxDestination &dest)
Definition: base58.cpp:329
Interface from Qt to configuration data structure for Bitcoin client.
Definition: optionsmodel.h:25
const CChainParams & Params()
Return the currently selected parameters.
Serialized script, used inside transaction inputs and outputs.
Definition: script.h:389
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: util.cpp:808
A reference to a CKey: the Hash160 of its serialized public key.
Definition: pubkey.h:20
const QString BITCOIN_IPC_PREFIX("dash:")
static QString ipcServerName()
A CWallet is an extension of a keystore, which also maintains a set of transactions and balances...
Definition: wallet.h:715
void handleURIConnection()
static const std::string TESTNET
bool IsDust(const CTxOut &txout, const CFeeRate &dustRelayFeeIn)
Definition: policy.cpp:35
size_type size() const
Definition: prevector.h:310
static bool verifySize(qint64 requestSize)
OptionsModel * optionsModel
const fs::path & GetDataDir(bool fNetSpecific)
Definition: util.cpp:928
static QString formatWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=separatorStandard)
Format as string (with unit)
QString boostPathToQString(const fs::path &path)
Definition: guiutil.cpp:1828
QString authenticatedMerchant
Definition: walletmodel.h:60
CFeeRate dustRelayFee
Definition: policy.cpp:177
Released under the MIT license