diff --git a/src/fs.cpp b/src/fs.cpp index debf43e83..752a442e1 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -1,250 +1,252 @@ // Copyright (c) 2017 The Bitcoin Core developers // Copyright (c) 2019 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #ifndef WIN32 +#include #include #include #include +#include #else #ifndef NOMINMAX #define NOMINMAX #endif #include #include #endif namespace fsbridge { FILE *fopen(const fs::path &p, const char *mode) { #ifndef WIN32 return ::fopen(p.string().c_str(), mode); #else std::wstring_convert, wchar_t> utf8_cvt; return ::_wfopen(p.wstring().c_str(), utf8_cvt.from_bytes(mode).c_str()); #endif } #ifndef WIN32 static std::string GetErrorReason() { return std::strerror(errno); } FileLock::FileLock(const fs::path &file) { fd = open(file.string().c_str(), O_RDWR); if (fd == -1) { reason = GetErrorReason(); } } FileLock::~FileLock() { if (fd != -1) { close(fd); } } static bool IsWSL() { struct utsname uname_data; return uname(&uname_data) == 0 && std::string(uname_data.version).find("Microsoft") != std::string::npos; } bool FileLock::TryLock() { if (fd == -1) { return false; } // Exclusive file locking is broken on WSL using fcntl (issue #18622) // This workaround can be removed once the bug on WSL is fixed static const bool is_wsl = IsWSL(); if (is_wsl) { if (flock(fd, LOCK_EX | LOCK_NB) == -1) { reason = GetErrorReason(); return false; } } else { struct flock lock; lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(fd, F_SETLK, &lock) == -1) { reason = GetErrorReason(); return false; } } return true; } #else static std::string GetErrorReason() { wchar_t *err; FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&err), 0, nullptr); std::wstring err_str(err); LocalFree(err); return std::wstring_convert>().to_bytes( err_str); } FileLock::FileLock(const fs::path &file) { hFile = CreateFileW(file.wstring().c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile == INVALID_HANDLE_VALUE) { reason = GetErrorReason(); } } FileLock::~FileLock() { if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); } } bool FileLock::TryLock() { if (hFile == INVALID_HANDLE_VALUE) { return false; } _OVERLAPPED overlapped = {0}; if (!LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, std::numeric_limits::max(), std::numeric_limits::max(), &overlapped)) { reason = GetErrorReason(); return false; } return true; } #endif std::string get_filesystem_error_message(const fs::filesystem_error &e) { #ifndef WIN32 return e.what(); #else // Convert from Multi Byte to utf-16 std::string mb_string(e.what()); int size = MultiByteToWideChar(CP_ACP, 0, mb_string.data(), mb_string.size(), nullptr, 0); std::wstring utf16_string(size, L'\0'); MultiByteToWideChar(CP_ACP, 0, mb_string.data(), mb_string.size(), &*utf16_string.begin(), size); // Convert from utf-16 to utf-8 return std::wstring_convert, wchar_t>() .to_bytes(utf16_string); #endif } #ifdef WIN32 #ifdef __GLIBCXX__ // reference: // https://github.com/gcc-mirror/gcc/blob/gcc-7_3_0-release/libstdc%2B%2B-v3/include/std/fstream#L270 static std::string openmodeToStr(std::ios_base::openmode mode) { switch (mode & ~std::ios_base::ate) { case std::ios_base::out: case std::ios_base::out | std::ios_base::trunc: return "w"; case std::ios_base::out | std::ios_base::app: case std::ios_base::app: return "a"; case std::ios_base::in: return "r"; case std::ios_base::in | std::ios_base::out: return "r+"; case std::ios_base::in | std::ios_base::out | std::ios_base::trunc: return "w+"; case std::ios_base::in | std::ios_base::out | std::ios_base::app: case std::ios_base::in | std::ios_base::app: return "a+"; case std::ios_base::out | std::ios_base::binary: case std::ios_base::out | std::ios_base::trunc | std::ios_base::binary: return "wb"; case std::ios_base::out | std::ios_base::app | std::ios_base::binary: case std::ios_base::app | std::ios_base::binary: return "ab"; case std::ios_base::in | std::ios_base::binary: return "rb"; case std::ios_base::in | std::ios_base::out | std::ios_base::binary: return "r+b"; case std::ios_base::in | std::ios_base::out | std::ios_base::trunc | std::ios_base::binary: return "w+b"; case std::ios_base::in | std::ios_base::out | std::ios_base::app | std::ios_base::binary: case std::ios_base::in | std::ios_base::app | std::ios_base::binary: return "a+b"; default: return std::string(); } } void ifstream::open(const fs::path &p, std::ios_base::openmode mode) { close(); mode |= std::ios_base::in; m_file = fsbridge::fopen(p, openmodeToStr(mode).c_str()); if (m_file == nullptr) { return; } m_filebuf = __gnu_cxx::stdio_filebuf(m_file, mode); rdbuf(&m_filebuf); if (mode & std::ios_base::ate) { seekg(0, std::ios_base::end); } } void ifstream::close() { if (m_file != nullptr) { m_filebuf.close(); fclose(m_file); } m_file = nullptr; } void ofstream::open(const fs::path &p, std::ios_base::openmode mode) { close(); mode |= std::ios_base::out; m_file = fsbridge::fopen(p, openmodeToStr(mode).c_str()); if (m_file == nullptr) { return; } m_filebuf = __gnu_cxx::stdio_filebuf(m_file, mode); rdbuf(&m_filebuf); if (mode & std::ios_base::ate) { seekp(0, std::ios_base::end); } } void ofstream::close() { if (m_file != nullptr) { m_filebuf.close(); fclose(m_file); } m_file = nullptr; } #else // __GLIBCXX__ static_assert( sizeof(*fs::path().BOOST_FILESYSTEM_C_STR) == sizeof(wchar_t), "Warning: This build is using boost::filesystem ofstream and ifstream " "implementations which will fail to open paths containing multibyte " "characters. You should delete this static_assert to ignore this warning, " "or switch to a different C++ standard library like the Microsoft C++ " "Standard Library (where boost uses non-standard extensions to construct " "stream objects with wide filenames), or the GNU libstdc++ library (where " "a more complicated workaround has been implemented above)."); #endif // __GLIBCXX__ #endif // WIN32 } // namespace fsbridge diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 9667147db..ad41b7f67 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -1,814 +1,812 @@ // 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. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QList CoinControlDialog::payAmounts; bool CoinControlDialog::fSubtractFeeFromAmount = false; bool CCoinControlWidgetItem::operator<(const QTreeWidgetItem &other) const { int column = treeWidget()->sortColumn(); if (column == CoinControlDialog::COLUMN_AMOUNT || column == CoinControlDialog::COLUMN_DATE || column == CoinControlDialog::COLUMN_CONFIRMATIONS) { return data(column, Qt::UserRole).toLongLong() < other.data(column, Qt::UserRole).toLongLong(); } return QTreeWidgetItem::operator<(other); } CoinControlDialog::CoinControlDialog(CCoinControl &coin_control, WalletModel *_model, const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::CoinControlDialog), m_coin_control(coin_control), model(_model), platformStyle(_platformStyle) { ui->setupUi(this); // context menu actions QAction *copyAddressAction = new QAction(tr("Copy address"), this); QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); // we need to enable/disable this copyTransactionHashAction = new QAction(tr("Copy transaction ID"), this); // we need to enable/disable this lockAction = new QAction(tr("Lock unspent"), this); // we need to enable/disable this unlockAction = new QAction(tr("Unlock unspent"), this); // context menu contextMenu = new QMenu(this); contextMenu->addAction(copyAddressAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(copyAmountAction); contextMenu->addAction(copyTransactionHashAction); contextMenu->addSeparator(); contextMenu->addAction(lockAction); contextMenu->addAction(unlockAction); // context menu signals connect(ui->treeWidget, &QWidget::customContextMenuRequested, this, &CoinControlDialog::showMenu); connect(copyAddressAction, &QAction::triggered, this, &CoinControlDialog::copyAddress); connect(copyLabelAction, &QAction::triggered, this, &CoinControlDialog::copyLabel); connect(copyAmountAction, &QAction::triggered, this, &CoinControlDialog::copyAmount); connect(copyTransactionHashAction, &QAction::triggered, this, &CoinControlDialog::copyTransactionHash); connect(lockAction, &QAction::triggered, this, &CoinControlDialog::lockCoin); connect(unlockAction, &QAction::triggered, this, &CoinControlDialog::unlockCoin); // clipboard actions QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this); QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); connect(clipboardQuantityAction, &QAction::triggered, this, &CoinControlDialog::clipboardQuantity); connect(clipboardAmountAction, &QAction::triggered, this, &CoinControlDialog::clipboardAmount); connect(clipboardFeeAction, &QAction::triggered, this, &CoinControlDialog::clipboardFee); connect(clipboardAfterFeeAction, &QAction::triggered, this, &CoinControlDialog::clipboardAfterFee); connect(clipboardBytesAction, &QAction::triggered, this, &CoinControlDialog::clipboardBytes); connect(clipboardLowOutputAction, &QAction::triggered, this, &CoinControlDialog::clipboardLowOutput); connect(clipboardChangeAction, &QAction::triggered, this, &CoinControlDialog::clipboardChange); ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); ui->labelCoinControlAmount->addAction(clipboardAmountAction); ui->labelCoinControlFee->addAction(clipboardFeeAction); ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); ui->labelCoinControlBytes->addAction(clipboardBytesAction); ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); ui->labelCoinControlChange->addAction(clipboardChangeAction); // toggle tree/list mode connect(ui->radioTreeMode, &QRadioButton::toggled, this, &CoinControlDialog::radioTreeMode); connect(ui->radioListMode, &QRadioButton::toggled, this, &CoinControlDialog::radioListMode); // click on checkbox connect(ui->treeWidget, &QTreeWidget::itemChanged, this, &CoinControlDialog::viewItemChanged); // click on header ui->treeWidget->header()->setSectionsClickable(true); connect(ui->treeWidget->header(), &QHeaderView::sectionClicked, this, &CoinControlDialog::headerSectionClicked); // ok button connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &CoinControlDialog::buttonBoxClicked); // (un)select all connect(ui->pushButtonSelectAll, &QPushButton::clicked, this, &CoinControlDialog::buttonSelectAllClicked); ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84); ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 110); ui->treeWidget->setColumnWidth(COLUMN_LABEL, 190); ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 320); ui->treeWidget->setColumnWidth(COLUMN_DATE, 130); ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 110); // default view is sorted by amount desc sortView(COLUMN_AMOUNT, Qt::DescendingOrder); // restore list mode and sortorder as a convenience feature QSettings settings; if (settings.contains("nCoinControlMode") && !settings.value("nCoinControlMode").toBool()) { ui->radioTreeMode->click(); } if (settings.contains("nCoinControlSortColumn") && settings.contains("nCoinControlSortOrder")) { sortView(settings.value("nCoinControlSortColumn").toInt(), (static_cast( settings.value("nCoinControlSortOrder").toInt()))); } GUIUtil::handleCloseWindowShortcut(this); if (_model->getOptionsModel() && _model->getAddressTableModel()) { updateView(); updateLabelLocked(); CoinControlDialog::updateLabels(m_coin_control, _model, this); } } CoinControlDialog::~CoinControlDialog() { QSettings settings; settings.setValue("nCoinControlMode", ui->radioListMode->isChecked()); settings.setValue("nCoinControlSortColumn", sortColumn); settings.setValue("nCoinControlSortOrder", (int)sortOrder); delete ui; } // ok button void CoinControlDialog::buttonBoxClicked(QAbstractButton *button) { if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { // closes the dialog done(QDialog::Accepted); } } // (un)select all void CoinControlDialog::buttonSelectAllClicked() { Qt::CheckState state = Qt::Checked; for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked) { state = Qt::Unchecked; break; } } ui->treeWidget->setEnabled(false); for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state) { ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state); } } ui->treeWidget->setEnabled(true); if (state == Qt::Unchecked) { // just to be sure m_coin_control.UnSelectAll(); } CoinControlDialog::updateLabels(m_coin_control, model, this); } // context menu void CoinControlDialog::showMenu(const QPoint &point) { QTreeWidgetItem *item = ui->treeWidget->itemAt(point); if (item) { contextMenuItem = item; // disable some items (like Copy Transaction ID, lock, unlock) for tree // roots in context menu if (item->data(COLUMN_ADDRESS, TxIdRole).toString().length() == 64) { COutPoint outpoint = buildOutPoint(item); // transaction hash is 64 characters (this means it is a child node, // so it is not a parent node in tree mode) copyTransactionHashAction->setEnabled(true); if (model->wallet().isLockedCoin(outpoint)) { lockAction->setEnabled(false); unlockAction->setEnabled(true); } else { lockAction->setEnabled(true); unlockAction->setEnabled(false); } } else { // this means click on parent node in tree mode -> disable all copyTransactionHashAction->setEnabled(false); lockAction->setEnabled(false); unlockAction->setEnabled(false); } // show context menu contextMenu->exec(QCursor::pos()); } } // context menu action: copy amount void CoinControlDialog::copyAmount() { GUIUtil::setClipboard( BitcoinUnits::removeSpaces(contextMenuItem->text(COLUMN_AMOUNT))); } // context menu action: copy label void CoinControlDialog::copyLabel() { if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_LABEL).length() == 0 && contextMenuItem->parent()) { GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_LABEL)); } else { GUIUtil::setClipboard(contextMenuItem->text(COLUMN_LABEL)); } } // context menu action: copy address void CoinControlDialog::copyAddress() { if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_ADDRESS).length() == 0 && contextMenuItem->parent()) { GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_ADDRESS)); } else { GUIUtil::setClipboard(contextMenuItem->text(COLUMN_ADDRESS)); } } // context menu action: copy transaction id void CoinControlDialog::copyTransactionHash() { GUIUtil::setClipboard( contextMenuItem->data(COLUMN_ADDRESS, TxIdRole).toString()); } // context menu action: lock coin void CoinControlDialog::lockCoin() { if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) { contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); } COutPoint outpoint = buildOutPoint(contextMenuItem); model->wallet().lockCoin(outpoint); contextMenuItem->setDisabled(true); contextMenuItem->setIcon( COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); updateLabelLocked(); } // context menu action: unlock coin void CoinControlDialog::unlockCoin() { COutPoint outpoint = buildOutPoint(contextMenuItem); model->wallet().unlockCoin(outpoint); contextMenuItem->setDisabled(false); contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); updateLabelLocked(); } // copy label "Quantity" to clipboard void CoinControlDialog::clipboardQuantity() { GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); } // copy label "Amount" to clipboard void CoinControlDialog::clipboardAmount() { GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left( ui->labelCoinControlAmount->text().indexOf(" "))); } // copy label "Fee" to clipboard void CoinControlDialog::clipboardFee() { GUIUtil::setClipboard( ui->labelCoinControlFee->text() .left(ui->labelCoinControlFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // copy label "After fee" to clipboard void CoinControlDialog::clipboardAfterFee() { GUIUtil::setClipboard( ui->labelCoinControlAfterFee->text() .left(ui->labelCoinControlAfterFee->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // copy label "Bytes" to clipboard void CoinControlDialog::clipboardBytes() { GUIUtil::setClipboard( ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, "")); } // copy label "Dust" to clipboard void CoinControlDialog::clipboardLowOutput() { GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); } // copy label "Change" to clipboard void CoinControlDialog::clipboardChange() { GUIUtil::setClipboard( ui->labelCoinControlChange->text() .left(ui->labelCoinControlChange->text().indexOf(" ")) .replace(ASYMP_UTF8, "")); } // treeview: sort void CoinControlDialog::sortView(int column, Qt::SortOrder order) { sortColumn = column; sortOrder = order; ui->treeWidget->sortItems(column, order); ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); } // treeview: clicked on header void CoinControlDialog::headerSectionClicked(int logicalIndex) { // click on most left column -> do nothing if (logicalIndex == COLUMN_CHECKBOX) { ui->treeWidget->header()->setSortIndicator(sortColumn, sortOrder); } else { if (sortColumn == logicalIndex) { sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder); } else { sortColumn = logicalIndex; // if label or address then default => asc, else default => desc sortOrder = ((sortColumn == COLUMN_LABEL || sortColumn == COLUMN_ADDRESS) ? Qt::AscendingOrder : Qt::DescendingOrder); } sortView(sortColumn, sortOrder); } } // toggle tree mode void CoinControlDialog::radioTreeMode(bool checked) { if (checked && model) { updateView(); } } // toggle list mode void CoinControlDialog::radioListMode(bool checked) { if (checked && model) { updateView(); } } // checkbox clicked by user void CoinControlDialog::viewItemChanged(QTreeWidgetItem *item, int column) { // transaction hash is 64 characters (this means it is a child node, so it // is not a parent node in tree mode) if (column == COLUMN_CHECKBOX && item->data(COLUMN_ADDRESS, TxIdRole).toString().length() == 64) { COutPoint outpoint = buildOutPoint(item); if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) { m_coin_control.UnSelect(outpoint); } else if (item->isDisabled()) { // locked (this happens if "check all" through parent node) item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); } else { m_coin_control.Select(outpoint); } // selection changed -> update labels if (ui->treeWidget->isEnabled()) { // do not update on every click for (un)select all CoinControlDialog::updateLabels(m_coin_control, model, this); } } } // shows count of locked unspent outputs void CoinControlDialog::updateLabelLocked() { std::vector vOutpts; model->wallet().listLockedCoins(vOutpts); if (vOutpts.size() > 0) { ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); ui->labelLocked->setVisible(true); } else { ui->labelLocked->setVisible(false); } } void CoinControlDialog::updateLabels(CCoinControl &m_coin_control, WalletModel *model, QDialog *dialog) { if (!model) { return; } // nPayAmount Amount nPayAmount = Amount::zero(); bool fDust = false; - CMutableTransaction txDummy; for (const Amount &amount : CoinControlDialog::payAmounts) { nPayAmount += amount; if (amount > Amount::zero()) { // Assumes a p2pkh script size CTxOut txout(amount, CScript() << std::vector(24, 0)); - txDummy.vout.push_back(txout); fDust |= IsDust(txout, model->node().getDustRelayFee()); } } Amount nAmount = Amount::zero(); Amount nPayFee = Amount::zero(); Amount nAfterFee = Amount::zero(); Amount nChange = Amount::zero(); unsigned int nBytes = 0; unsigned int nBytesInputs = 0; unsigned int nQuantity = 0; std::vector vCoinControl; m_coin_control.ListSelected(vCoinControl); size_t i = 0; for (const auto &out : model->wallet().getCoins(vCoinControl)) { if (out.depth_in_main_chain < 0) { continue; } // unselect already spent, very unlikely scenario, this could happen // when selected are spent elsewhere, like rpc or another computer const COutPoint &output = vCoinControl[i++]; if (out.is_spent) { m_coin_control.UnSelect(output); continue; } // Quantity nQuantity++; // Amount nAmount += out.txout.nValue; // Bytes CTxDestination address; if (ExtractDestination(out.txout.scriptPubKey, address)) { CPubKey pubkey; PKHash *pkhash = boost::get(&address); if (pkhash && model->wallet().getPubKey(out.txout.scriptPubKey, ToKeyID(*pkhash), pubkey)) { nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); } else { // in all error cases, simply assume 148 here nBytesInputs += 148; } } else { nBytesInputs += 148; } } // calculation if (nQuantity > 0) { // Bytes // always assume +1 output for change here nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // in the subtract fee from amount case, we can tell if zero change // already and subtract the bytes, so that fee calculation afterwards is // accurate if (CoinControlDialog::fSubtractFeeFromAmount) { if (nAmount - nPayAmount == Amount::zero()) { nBytes -= 34; } } // Fee nPayFee = model->wallet().getMinimumFee(nBytes, m_coin_control); if (nPayAmount > Amount::zero()) { nChange = nAmount - nPayAmount; if (!CoinControlDialog::fSubtractFeeFromAmount) { nChange -= nPayFee; } // Never create dust outputs; if we would, just add the dust to the // fee. if (nChange > Amount::zero() && nChange < MIN_CHANGE) { // Assumes a p2pkh script size CTxOut txout(nChange, CScript() << std::vector(24, 0)); if (IsDust(txout, model->node().getDustRelayFee())) { nPayFee += nChange; nChange = Amount::zero(); if (CoinControlDialog::fSubtractFeeFromAmount) { // we didn't detect lack of change above nBytes -= 34; } } } if (nChange == Amount::zero() && !CoinControlDialog::fSubtractFeeFromAmount) { nBytes -= 34; } } // after fee nAfterFee = std::max(nAmount - nPayFee, Amount::zero()); } // actually update labels int nDisplayUnit = BitcoinUnits::BCH; if (model && model->getOptionsModel()) { nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); } QLabel *l1 = dialog->findChild("labelCoinControlQuantity"); QLabel *l2 = dialog->findChild("labelCoinControlAmount"); QLabel *l3 = dialog->findChild("labelCoinControlFee"); QLabel *l4 = dialog->findChild("labelCoinControlAfterFee"); QLabel *l5 = dialog->findChild("labelCoinControlBytes"); QLabel *l7 = dialog->findChild("labelCoinControlLowOutput"); QLabel *l8 = dialog->findChild("labelCoinControlChange"); // enable/disable "dust" and "change" dialog->findChild("labelCoinControlLowOutputText") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild("labelCoinControlLowOutput") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild("labelCoinControlChangeText") ->setEnabled(nPayAmount > Amount::zero()); dialog->findChild("labelCoinControlChange") ->setEnabled(nPayAmount > Amount::zero()); // stats // Quantity l1->setText(QString::number(nQuantity)); // Amount l2->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAmount)); // Fee l3->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nPayFee)); // After Fee l4->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // Bytes l5->setText(((nBytes > 0) ? ASYMP_UTF8 : "") + QString::number(nBytes)); // Dust l7->setText(fDust ? tr("yes") : tr("no")); // Change l8->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nChange)); if (nPayFee > Amount::zero()) { l3->setText(ASYMP_UTF8 + l3->text()); l4->setText(ASYMP_UTF8 + l4->text()); if (nChange > Amount::zero() && !CoinControlDialog::fSubtractFeeFromAmount) { l8->setText(ASYMP_UTF8 + l8->text()); } } // turn label red when dust l7->setStyleSheet((fDust) ? "color:red;" : ""); // tool tips QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller " "than the current dust threshold."); // how many satoshis the estimated fee can vary per byte we guess wrong double dFeeVary = (nBytes != 0) ? double(nPayFee / SATOSHI) / nBytes : 0; QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); l3->setToolTip(toolTip4); l4->setToolTip(toolTip4); l7->setToolTip(toolTipDust); l8->setToolTip(toolTip4); dialog->findChild("labelCoinControlFeeText") ->setToolTip(l3->toolTip()); dialog->findChild("labelCoinControlAfterFeeText") ->setToolTip(l4->toolTip()); dialog->findChild("labelCoinControlBytesText") ->setToolTip(l5->toolTip()); dialog->findChild("labelCoinControlLowOutputText") ->setToolTip(l7->toolTip()); dialog->findChild("labelCoinControlChangeText") ->setToolTip(l8->toolTip()); // Insufficient funds QLabel *label = dialog->findChild("labelCoinControlInsuffFunds"); if (label) { label->setVisible(nChange < Amount::zero()); } } COutPoint CoinControlDialog::buildOutPoint(const QTreeWidgetItem *item) { TxId txid; txid.SetHex(item->data(COLUMN_ADDRESS, TxIdRole).toString().toStdString()); return COutPoint(txid, item->data(COLUMN_ADDRESS, VOutRole).toUInt()); } void CoinControlDialog::updateView() { if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) { return; } bool treeMode = ui->radioTreeMode->isChecked(); ui->treeWidget->clear(); // performance, otherwise updateLabels would be called for every checked // checkbox ui->treeWidget->setEnabled(false); ui->treeWidget->setAlternatingRowColors(!treeMode); QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; int nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); for (const auto &coins : model->wallet().listCoins()) { CCoinControlWidgetItem *itemWalletAddress{nullptr}; QString sWalletAddress = QString::fromStdString( EncodeCashAddr(coins.first, model->getChainParams())); QString sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress); if (sWalletLabel.isEmpty()) { sWalletLabel = tr("(no label)"); } if (treeMode) { // wallet address itemWalletAddress = new CCoinControlWidgetItem(ui->treeWidget); itemWalletAddress->setFlags(flgTristate); itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // label itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel); // address itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress); } Amount nSum = Amount::zero(); int nChildren = 0; for (const auto &outpair : coins.second) { const COutPoint &output = std::get<0>(outpair); const interfaces::WalletTxOut &out = std::get<1>(outpair); nSum += out.txout.nValue; nChildren++; CCoinControlWidgetItem *itemOutput; if (treeMode) { itemOutput = new CCoinControlWidgetItem(itemWalletAddress); } else { itemOutput = new CCoinControlWidgetItem(ui->treeWidget); } itemOutput->setFlags(flgCheckbox); itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // address CTxDestination outputAddress; QString sAddress = ""; if (ExtractDestination(out.txout.scriptPubKey, outputAddress)) { sAddress = QString::fromStdString( EncodeCashAddr(outputAddress, model->getChainParams())); // if listMode or change => show bitcoin address. In tree mode, // address is not shown again for direct wallet address outputs if (!treeMode || (!(sAddress == sWalletAddress))) { itemOutput->setText(COLUMN_ADDRESS, sAddress); } } // label if (!(sAddress == sWalletAddress)) { // change tooltip from where the change comes from itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)") .arg(sWalletLabel) .arg(sWalletAddress)); itemOutput->setText(COLUMN_LABEL, tr("(change)")); } else if (!treeMode) { QString sLabel = model->getAddressTableModel()->labelForAddress(sAddress); if (sLabel.isEmpty()) { sLabel = tr("(no label)"); } itemOutput->setText(COLUMN_LABEL, sLabel); } // amount itemOutput->setText( COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.txout.nValue)); // padding so that sorting works correctly itemOutput->setData( COLUMN_AMOUNT, Qt::UserRole, QVariant(qlonglong(out.txout.nValue / SATOSHI))); // date itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.time)); itemOutput->setData(COLUMN_DATE, Qt::UserRole, QVariant((qlonglong)out.time)); // confirmations itemOutput->setText(COLUMN_CONFIRMATIONS, QString::number(out.depth_in_main_chain)); itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole, QVariant((qlonglong)out.depth_in_main_chain)); // transaction id itemOutput->setData( COLUMN_ADDRESS, TxIdRole, QString::fromStdString(output.GetTxId().GetHex())); // vout index itemOutput->setData(COLUMN_ADDRESS, VOutRole, output.GetN()); // disable locked coins if (model->wallet().isLockedCoin(output)) { // just to be sure m_coin_control.UnSelect(output); itemOutput->setDisabled(true); itemOutput->setIcon( COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed")); } // set checkbox if (m_coin_control.IsSelected(output)) { itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); } } // amount if (treeMode) { itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); itemWalletAddress->setText( COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum)); itemWalletAddress->setData(COLUMN_AMOUNT, Qt::UserRole, QVariant(qlonglong(nSum / SATOSHI))); } } // expand all partially selected if (treeMode) { for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) { if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) { ui->treeWidget->topLevelItem(i)->setExpanded(true); } } } // sort view sortView(sortColumn, sortOrder); ui->treeWidget->setEnabled(true); } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index d27543edb..fa45c4cd9 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -1,227 +1,226 @@ // 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 #include #include // For MAX_BLOCK_TIME_GAP #include // For Params() #include #include #include #include #include /** * Return positive answer if transaction should be shown in list. */ bool TransactionRecord::showTransaction() { // There are currently no cases where we hide transactions, but we may want // to use this in the future for things like RBF. return true; } /** * Decompose CWallet transaction to model transaction records. */ QList TransactionRecord::decomposeTransaction(const interfaces::WalletTx &wtx) { QList parts; int64_t nTime = wtx.time; Amount nCredit = wtx.credit; Amount nDebit = wtx.debit; Amount nNet = nCredit - nDebit; const TxId &txid = wtx.tx->GetId(); std::map mapValue = wtx.value_map; if (nNet > Amount::zero() || wtx.is_coinbase) { // // Credit // for (size_t i = 0; i < wtx.tx->vout.size(); i++) { const CTxOut &txout = wtx.tx->vout[i]; isminetype mine = wtx.txout_is_mine[i]; if (mine) { TransactionRecord sub(txid, nTime); - CTxDestination address; sub.idx = i; // vout index sub.credit = txout.nValue; sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY; if (wtx.txout_address_is_mine[i]) { // Received by Bitcoin Address sub.type = TransactionRecord::RecvWithAddress; sub.address = EncodeCashAddr(wtx.txout_address[i], Params()); } else { // Received by IP connection (deprecated features), or a // multisignature or other non-simple transaction sub.type = TransactionRecord::RecvFromOther; sub.address = mapValue["from"]; } if (wtx.is_coinbase) { // Generated sub.type = TransactionRecord::Generated; } parts.append(sub); } } } else { bool involvesWatchAddress = false; isminetype fAllFromMe = ISMINE_SPENDABLE; for (const isminetype mine : wtx.txin_is_mine) { if (mine & ISMINE_WATCH_ONLY) { involvesWatchAddress = true; } if (fAllFromMe > mine) { fAllFromMe = mine; } } isminetype fAllToMe = ISMINE_SPENDABLE; for (const isminetype mine : wtx.txout_is_mine) { if (mine & ISMINE_WATCH_ONLY) { involvesWatchAddress = true; } if (fAllToMe > mine) { fAllToMe = mine; } } if (fAllFromMe && fAllToMe) { // Payment to self std::string address; for (auto it = wtx.txout_address.begin(); it != wtx.txout_address.end(); ++it) { if (it != wtx.txout_address.begin()) { address += ", "; } address += EncodeCashAddr(*it, Params()); } Amount nChange = wtx.change; parts.append(TransactionRecord( txid, nTime, TransactionRecord::SendToSelf, address, -(nDebit - nChange), nCredit - nChange)); // maybe pass to TransactionRecord as constructor argument parts.last().involvesWatchAddress = involvesWatchAddress; } else if (fAllFromMe) { // // Debit // Amount nTxFee = nDebit - wtx.tx->GetValueOut(); for (size_t nOut = 0; nOut < wtx.tx->vout.size(); nOut++) { const CTxOut &txout = wtx.tx->vout[nOut]; TransactionRecord sub(txid, nTime); sub.idx = nOut; sub.involvesWatchAddress = involvesWatchAddress; if (wtx.txout_is_mine[nOut]) { // Ignore parts sent to self, as this is usually the change // from a transaction sent back to our own address. continue; } if (!boost::get(&wtx.txout_address[nOut])) { // Sent to Bitcoin Address sub.type = TransactionRecord::SendToAddress; sub.address = EncodeCashAddr(wtx.txout_address[nOut], Params()); } else { // Sent to IP, or other non-address transaction like OP_EVAL sub.type = TransactionRecord::SendToOther; sub.address = mapValue["to"]; } Amount nValue = txout.nValue; /* Add fee to first output */ if (nTxFee > Amount::zero()) { nValue += nTxFee; nTxFee = Amount::zero(); } sub.debit = -1 * nValue; parts.append(sub); } } else { // // Mixed debit transaction, can't break down payees // parts.append(TransactionRecord(txid, nTime, TransactionRecord::Other, "", nNet, Amount::zero())); parts.last().involvesWatchAddress = involvesWatchAddress; } } return parts; } void TransactionRecord::updateStatus(const interfaces::WalletTxStatus &wtx, const BlockHash &block_hash, int numBlocks, int64_t block_time) { // Determine transaction status // Sort order, unrecorded transactions sort to the top status.sortKey = strprintf("%010d-%01d-%010u-%03d", wtx.block_height, wtx.is_coinbase ? 1 : 0, wtx.time_received, idx); status.countsForBalance = wtx.is_trusted && !(wtx.blocks_to_maturity > 0); status.depth = wtx.depth_in_main_chain; status.m_cur_block_hash = block_hash; const bool up_to_date = (int64_t(QDateTime::currentMSecsSinceEpoch()) / 1000 - block_time < MAX_BLOCK_TIME_GAP); if (up_to_date && !wtx.is_final) { if (wtx.lock_time < LOCKTIME_THRESHOLD) { status.status = TransactionStatus::OpenUntilBlock; status.open_for = wtx.lock_time - numBlocks; } else { status.status = TransactionStatus::OpenUntilDate; status.open_for = wtx.lock_time; } } else if (type == TransactionRecord::Generated) { // For generated transactions, determine maturity if (wtx.blocks_to_maturity > 0) { status.status = TransactionStatus::Immature; if (wtx.is_in_main_chain) { status.matures_in = wtx.blocks_to_maturity; } else { status.status = TransactionStatus::NotAccepted; } } else { status.status = TransactionStatus::Confirmed; } } else { if (status.depth < 0) { status.status = TransactionStatus::Conflicted; } else if (status.depth == 0) { status.status = TransactionStatus::Unconfirmed; if (wtx.is_abandoned) { status.status = TransactionStatus::Abandoned; } } else if (status.depth < RecommendedNumConfirmations) { status.status = TransactionStatus::Confirming; } else { status.status = TransactionStatus::Confirmed; } } } bool TransactionRecord::statusUpdateNeeded(const BlockHash &block_hash) const { assert(!block_hash.IsNull()); return status.m_cur_block_hash != block_hash; } QString TransactionRecord::getTxID() const { return QString::fromStdString(txid.ToString()); } int TransactionRecord::getOutputIndex() const { return idx; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index bc9940bfa..664c836dd 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1,5046 +1,5045 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include