2017-11-10 01:57:53 +01:00
|
|
|
#include <qt/test/wallettests.h>
|
2017-03-10 21:58:53 +01:00
|
|
|
|
2017-11-10 01:57:53 +01:00
|
|
|
#include <qt/bitcoinamountfield.h>
|
|
|
|
#include <qt/callback.h>
|
|
|
|
#include <qt/optionsmodel.h>
|
|
|
|
#include <qt/platformstyle.h>
|
|
|
|
#include <qt/qvalidatedlineedit.h>
|
|
|
|
#include <qt/sendcoinsdialog.h>
|
|
|
|
#include <qt/sendcoinsentry.h>
|
|
|
|
#include <qt/transactiontablemodel.h>
|
|
|
|
#include <qt/transactionview.h>
|
|
|
|
#include <qt/walletmodel.h>
|
|
|
|
#include <test/test_bitcoin.h>
|
|
|
|
#include <validation.h>
|
|
|
|
#include <wallet/wallet.h>
|
|
|
|
#include <qt/overviewpage.h>
|
|
|
|
#include <qt/receivecoinsdialog.h>
|
|
|
|
#include <qt/recentrequeststablemodel.h>
|
|
|
|
#include <qt/receiverequestdialog.h>
|
2017-03-10 21:58:53 +01:00
|
|
|
|
2018-04-02 20:31:40 +02:00
|
|
|
#include <memory>
|
|
|
|
|
2017-03-10 21:58:53 +01:00
|
|
|
#include <QAbstractButton>
|
2017-03-09 23:56:23 +01:00
|
|
|
#include <QAction>
|
2017-03-10 21:58:53 +01:00
|
|
|
#include <QApplication>
|
2017-03-09 23:56:23 +01:00
|
|
|
#include <QCheckBox>
|
|
|
|
#include <QPushButton>
|
2017-03-10 21:58:53 +01:00
|
|
|
#include <QTimer>
|
|
|
|
#include <QVBoxLayout>
|
2017-05-31 07:22:56 +02:00
|
|
|
#include <QTextEdit>
|
|
|
|
#include <QListView>
|
|
|
|
#include <QDialogButtonBox>
|
2017-03-10 21:58:53 +01:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
2017-03-09 23:56:23 +01:00
|
|
|
//! Press "Ok" button in message box dialog.
|
|
|
|
void ConfirmMessage(QString* text = nullptr)
|
2017-03-10 21:58:53 +01:00
|
|
|
{
|
2017-03-09 23:56:23 +01:00
|
|
|
QTimer::singleShot(0, makeCallback([text](Callback* callback) {
|
|
|
|
for (QWidget* widget : QApplication::topLevelWidgets()) {
|
|
|
|
if (widget->inherits("QMessageBox")) {
|
|
|
|
QMessageBox* messageBox = qobject_cast<QMessageBox*>(widget);
|
|
|
|
if (text) *text = messageBox->text();
|
|
|
|
messageBox->defaultButton()->click();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete callback;
|
|
|
|
}), SLOT(call()));
|
|
|
|
}
|
|
|
|
|
|
|
|
//! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
|
|
|
|
void ConfirmSend(QString* text = nullptr, bool cancel = false)
|
|
|
|
{
|
|
|
|
QTimer::singleShot(0, makeCallback([text, cancel](Callback* callback) {
|
2017-03-10 21:58:53 +01:00
|
|
|
for (QWidget* widget : QApplication::topLevelWidgets()) {
|
|
|
|
if (widget->inherits("SendConfirmationDialog")) {
|
|
|
|
SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
|
2017-03-09 23:56:23 +01:00
|
|
|
if (text) *text = dialog->text();
|
|
|
|
QAbstractButton* button = dialog->button(cancel ? QMessageBox::Cancel : QMessageBox::Yes);
|
2017-03-10 21:58:53 +01:00
|
|
|
button->setEnabled(true);
|
|
|
|
button->click();
|
|
|
|
}
|
|
|
|
}
|
2017-03-27 20:34:38 +02:00
|
|
|
delete callback;
|
|
|
|
}), SLOT(call()));
|
2017-03-10 21:58:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//! Send coins to address and return txid.
|
2017-08-23 03:02:33 +02:00
|
|
|
uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDestination& address, CAmount amount, bool rbf)
|
2017-03-10 21:58:53 +01:00
|
|
|
{
|
|
|
|
QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>("entries");
|
|
|
|
SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
|
2017-08-23 03:02:33 +02:00
|
|
|
entry->findChild<QValidatedLineEdit*>("payTo")->setText(QString::fromStdString(EncodeDestination(address)));
|
2017-03-10 21:58:53 +01:00
|
|
|
entry->findChild<BitcoinAmountField*>("payAmount")->setValue(amount);
|
2017-03-09 23:56:23 +01:00
|
|
|
sendCoinsDialog.findChild<QFrame*>("frameFee")
|
|
|
|
->findChild<QFrame*>("frameFeeSelection")
|
|
|
|
->findChild<QCheckBox*>("optInRBF")
|
|
|
|
->setCheckState(rbf ? Qt::Checked : Qt::Unchecked);
|
2017-03-10 21:58:53 +01:00
|
|
|
uint256 txid;
|
2017-03-20 17:27:07 +01:00
|
|
|
boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](CWallet*, const uint256& hash, ChangeType status) {
|
2017-03-10 21:58:53 +01:00
|
|
|
if (status == CT_NEW) txid = hash;
|
2017-03-20 17:27:07 +01:00
|
|
|
}));
|
2017-03-10 21:58:53 +01:00
|
|
|
ConfirmSend();
|
|
|
|
QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked");
|
|
|
|
return txid;
|
|
|
|
}
|
|
|
|
|
|
|
|
//! Find index of txid in transaction list.
|
|
|
|
QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
|
|
|
|
{
|
|
|
|
QString hash = QString::fromStdString(txid.ToString());
|
|
|
|
int rows = model.rowCount({});
|
|
|
|
for (int row = 0; row < rows; ++row) {
|
|
|
|
QModelIndex index = model.index(row, 0, {});
|
|
|
|
if (model.data(index, TransactionTableModel::TxHashRole) == hash) {
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2017-05-25 13:58:52 +02:00
|
|
|
//! Request context menu (call method that is public in qt5, but protected in qt4).
|
|
|
|
void RequestContextMenu(QWidget* widget)
|
|
|
|
{
|
|
|
|
class Qt4Hack : public QWidget
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
using QWidget::customContextMenuRequested;
|
|
|
|
};
|
|
|
|
static_cast<Qt4Hack*>(widget)->customContextMenuRequested({});
|
|
|
|
}
|
|
|
|
|
2017-03-09 23:56:23 +01:00
|
|
|
//! Invoke bumpfee on txid and check results.
|
|
|
|
void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, std::string expectError, bool cancel)
|
|
|
|
{
|
|
|
|
QTableView* table = view.findChild<QTableView*>("transactionView");
|
|
|
|
QModelIndex index = FindTx(*table->selectionModel()->model(), txid);
|
|
|
|
QVERIFY2(index.isValid(), "Could not find BumpFee txid");
|
|
|
|
|
|
|
|
// Select row in table, invoke context menu, and make sure bumpfee action is
|
|
|
|
// enabled or disabled as expected.
|
|
|
|
QAction* action = view.findChild<QAction*>("bumpFeeAction");
|
|
|
|
table->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
|
|
|
action->setEnabled(expectDisabled);
|
2017-05-25 13:58:52 +02:00
|
|
|
RequestContextMenu(table);
|
2017-03-09 23:56:23 +01:00
|
|
|
QCOMPARE(action->isEnabled(), !expectDisabled);
|
|
|
|
|
|
|
|
action->setEnabled(true);
|
|
|
|
QString text;
|
|
|
|
if (expectError.empty()) {
|
|
|
|
ConfirmSend(&text, cancel);
|
|
|
|
} else {
|
|
|
|
ConfirmMessage(&text);
|
|
|
|
}
|
|
|
|
action->trigger();
|
|
|
|
QVERIFY(text.indexOf(QString::fromStdString(expectError)) != -1);
|
|
|
|
}
|
|
|
|
|
2017-03-10 21:58:53 +01:00
|
|
|
//! Simple qt wallet tests.
|
2017-04-03 17:07:40 +02:00
|
|
|
//
|
|
|
|
// Test widgets can be debugged interactively calling show() on them and
|
|
|
|
// manually running the event loop, e.g.:
|
|
|
|
//
|
|
|
|
// sendCoinsDialog.show();
|
|
|
|
// QEventLoop().exec();
|
|
|
|
//
|
|
|
|
// This also requires overriding the default minimal Qt platform:
|
|
|
|
//
|
|
|
|
// src/qt/test/test_bitcoin-qt -platform xcb # Linux
|
|
|
|
// src/qt/test/test_bitcoin-qt -platform windows # Windows
|
|
|
|
// src/qt/test/test_bitcoin-qt -platform cocoa # macOS
|
2017-05-31 07:22:56 +02:00
|
|
|
void TestGUI()
|
2017-03-10 21:58:53 +01:00
|
|
|
{
|
2017-12-01 01:49:11 +01:00
|
|
|
g_address_type = OUTPUT_TYPE_P2SH_SEGWIT;
|
|
|
|
g_change_type = OUTPUT_TYPE_P2SH_SEGWIT;
|
|
|
|
|
2017-03-09 23:56:23 +01:00
|
|
|
// Set up wallet and chain with 105 blocks (5 mature blocks for spending).
|
2017-03-10 21:58:53 +01:00
|
|
|
TestChain100Setup test;
|
2017-03-09 23:56:23 +01:00
|
|
|
for (int i = 0; i < 5; ++i) {
|
|
|
|
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
|
|
|
|
}
|
2017-03-10 21:58:53 +01:00
|
|
|
bitdb.MakeMock();
|
2017-03-08 11:48:58 +01:00
|
|
|
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
|
|
|
|
CWallet wallet(std::move(dbw));
|
2017-03-10 21:58:53 +01:00
|
|
|
bool firstRun;
|
|
|
|
wallet.LoadWallet(firstRun);
|
|
|
|
{
|
|
|
|
LOCK(wallet.cs_wallet);
|
2017-12-01 01:49:11 +01:00
|
|
|
wallet.SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), g_address_type), "", "receive");
|
2017-03-10 21:58:53 +01:00
|
|
|
wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey());
|
|
|
|
}
|
2017-11-06 23:08:55 +01:00
|
|
|
{
|
|
|
|
LOCK(cs_main);
|
2017-12-13 00:13:58 +01:00
|
|
|
WalletRescanReserver reserver(&wallet);
|
|
|
|
reserver.reserve();
|
|
|
|
wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true);
|
2017-11-06 23:08:55 +01:00
|
|
|
}
|
2017-03-10 21:58:53 +01:00
|
|
|
wallet.SetBroadcastTransactions(true);
|
|
|
|
|
|
|
|
// Create widgets for sending coins and listing transactions.
|
|
|
|
std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
|
|
|
|
SendCoinsDialog sendCoinsDialog(platformStyle.get());
|
2017-03-09 23:56:23 +01:00
|
|
|
TransactionView transactionView(platformStyle.get());
|
2017-03-10 21:58:53 +01:00
|
|
|
OptionsModel optionsModel;
|
|
|
|
WalletModel walletModel(platformStyle.get(), &wallet, &optionsModel);
|
|
|
|
sendCoinsDialog.setModel(&walletModel);
|
2017-03-09 23:56:23 +01:00
|
|
|
transactionView.setModel(&walletModel);
|
2017-03-10 21:58:53 +01:00
|
|
|
|
|
|
|
// Send two transactions, and verify they are added to transaction list.
|
|
|
|
TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
|
2017-03-09 23:56:23 +01:00
|
|
|
QCOMPARE(transactionTableModel->rowCount({}), 105);
|
2017-08-23 03:02:33 +02:00
|
|
|
uint256 txid1 = SendCoins(wallet, sendCoinsDialog, CKeyID(), 5 * COIN, false /* rbf */);
|
|
|
|
uint256 txid2 = SendCoins(wallet, sendCoinsDialog, CKeyID(), 10 * COIN, true /* rbf */);
|
2017-03-09 23:56:23 +01:00
|
|
|
QCOMPARE(transactionTableModel->rowCount({}), 107);
|
2017-03-10 21:58:53 +01:00
|
|
|
QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
|
|
|
|
QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
|
|
|
|
|
2017-03-09 23:56:23 +01:00
|
|
|
// Call bumpfee. Test disabled, canceled, enabled, then failing cases.
|
|
|
|
BumpFee(transactionView, txid1, true /* expect disabled */, "not BIP 125 replaceable" /* expected error */, false /* cancel */);
|
|
|
|
BumpFee(transactionView, txid2, false /* expect disabled */, {} /* expected error */, true /* cancel */);
|
|
|
|
BumpFee(transactionView, txid2, false /* expect disabled */, {} /* expected error */, false /* cancel */);
|
2017-06-01 21:35:44 +02:00
|
|
|
BumpFee(transactionView, txid2, true /* expect disabled */, "already bumped" /* expected error */, false /* cancel */);
|
2017-03-09 23:56:23 +01:00
|
|
|
|
2017-05-31 07:22:56 +02:00
|
|
|
// Check current balance on OverviewPage
|
|
|
|
OverviewPage overviewPage(platformStyle.get());
|
|
|
|
overviewPage.setWalletModel(&walletModel);
|
|
|
|
QLabel* balanceLabel = overviewPage.findChild<QLabel*>("labelBalance");
|
|
|
|
QString balanceText = balanceLabel->text();
|
|
|
|
int unit = walletModel.getOptionsModel()->getDisplayUnit();
|
|
|
|
CAmount balance = walletModel.getBalance();
|
|
|
|
QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::separatorAlways);
|
|
|
|
QCOMPARE(balanceText, balanceComparison);
|
|
|
|
|
|
|
|
// Check Request Payment button
|
|
|
|
ReceiveCoinsDialog receiveCoinsDialog(platformStyle.get());
|
|
|
|
receiveCoinsDialog.setModel(&walletModel);
|
|
|
|
RecentRequestsTableModel* requestTableModel = walletModel.getRecentRequestsTableModel();
|
|
|
|
|
|
|
|
// Label input
|
|
|
|
QLineEdit* labelInput = receiveCoinsDialog.findChild<QLineEdit*>("reqLabel");
|
|
|
|
labelInput->setText("TEST_LABEL_1");
|
|
|
|
|
|
|
|
// Amount input
|
|
|
|
BitcoinAmountField* amountInput = receiveCoinsDialog.findChild<BitcoinAmountField*>("reqAmount");
|
|
|
|
amountInput->setValue(1);
|
|
|
|
|
|
|
|
// Message input
|
|
|
|
QLineEdit* messageInput = receiveCoinsDialog.findChild<QLineEdit*>("reqMessage");
|
|
|
|
messageInput->setText("TEST_MESSAGE_1");
|
|
|
|
int initialRowCount = requestTableModel->rowCount({});
|
|
|
|
QPushButton* requestPaymentButton = receiveCoinsDialog.findChild<QPushButton*>("receiveButton");
|
|
|
|
requestPaymentButton->click();
|
|
|
|
for (QWidget* widget : QApplication::topLevelWidgets()) {
|
|
|
|
if (widget->inherits("ReceiveRequestDialog")) {
|
|
|
|
ReceiveRequestDialog* receiveRequestDialog = qobject_cast<ReceiveRequestDialog*>(widget);
|
|
|
|
QTextEdit* rlist = receiveRequestDialog->QObject::findChild<QTextEdit*>("outUri");
|
|
|
|
QString paymentText = rlist->toPlainText();
|
|
|
|
QStringList paymentTextList = paymentText.split('\n');
|
|
|
|
QCOMPARE(paymentTextList.at(0), QString("Payment information"));
|
|
|
|
QVERIFY(paymentTextList.at(1).indexOf(QString("URI: bitcoin:")) != -1);
|
|
|
|
QVERIFY(paymentTextList.at(2).indexOf(QString("Address:")) != -1);
|
|
|
|
QCOMPARE(paymentTextList.at(3), QString("Amount: 0.00000001 ") + QString::fromStdString(CURRENCY_UNIT));
|
|
|
|
QCOMPARE(paymentTextList.at(4), QString("Label: TEST_LABEL_1"));
|
|
|
|
QCOMPARE(paymentTextList.at(5), QString("Message: TEST_MESSAGE_1"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear button
|
|
|
|
QPushButton* clearButton = receiveCoinsDialog.findChild<QPushButton*>("clearButton");
|
|
|
|
clearButton->click();
|
|
|
|
QCOMPARE(labelInput->text(), QString(""));
|
|
|
|
QCOMPARE(amountInput->value(), CAmount(0));
|
|
|
|
QCOMPARE(messageInput->text(), QString(""));
|
|
|
|
|
|
|
|
// Check addition to history
|
|
|
|
int currentRowCount = requestTableModel->rowCount({});
|
|
|
|
QCOMPARE(currentRowCount, initialRowCount+1);
|
|
|
|
|
|
|
|
// Check Remove button
|
|
|
|
QTableView* table = receiveCoinsDialog.findChild<QTableView*>("recentRequestsView");
|
|
|
|
table->selectRow(currentRowCount-1);
|
|
|
|
QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>("removeRequestButton");
|
|
|
|
removeRequestButton->click();
|
|
|
|
QCOMPARE(requestTableModel->rowCount({}), currentRowCount-1);
|
|
|
|
|
2017-03-10 21:58:53 +01:00
|
|
|
bitdb.Flush(true);
|
|
|
|
bitdb.Reset();
|
|
|
|
}
|
2017-04-28 19:59:25 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void WalletTests::walletTests()
|
|
|
|
{
|
2017-05-31 07:22:56 +02:00
|
|
|
TestGUI();
|
2017-04-28 19:59:25 +02:00
|
|
|
}
|