diff --git a/src/coincontrol.h b/src/coincontrol.h index 5e8413e7fa86..4a7ce550a9e2 100644 --- a/src/coincontrol.h +++ b/src/coincontrol.h @@ -15,6 +15,8 @@ class CCoinControl CTxDestination destChange; bool useObfuScation; bool useSwiftTX; + bool fSplitBlock; + int nSplitBlock; //! If false, allows unselected inputs, but requires all selected inputs be used bool fAllowOtherInputs; //! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria @@ -36,6 +38,8 @@ class CCoinControl fAllowOtherInputs = false; fAllowWatchOnly = false; nMinimumTotalFee = 0; + fSplitBlock = false; + nSplitBlock = 1; } bool HasSelected() const diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 5c0be863f203..8776e37fb1c1 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -34,6 +34,7 @@ using namespace std; QList CoinControlDialog::payAmounts; +int CoinControlDialog::nSplitBlockDummy; CCoinControl* CoinControlDialog::coinControl = new CCoinControl(); CoinControlDialog::CoinControlDialog(QWidget *parent) : @@ -586,7 +587,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) if (nQuantity > 0) { // Bytes - nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here + nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + max(1, CoinControlDialog::nSplitBlockDummy) : 2) * 34) + 10; // always assume +1 output for change here // Priority double mempoolEstimatePriority = mempool.estimatePriority(nTxConfirmTarget); diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h index 7be04c1877f8..837b1f6e0aac 100644 --- a/src/qt/coincontroldialog.h +++ b/src/qt/coincontroldialog.h @@ -41,6 +41,7 @@ class CoinControlDialog : public QDialog static QList payAmounts; static CCoinControl *coinControl; + static int nSplitBlockDummy; private: Ui::CoinControlDialog *ui; diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 5a8325d2f07c..ed30f63abd4a 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -528,7 +528,7 @@ - + 12 @@ -564,6 +564,40 @@ + + + + Split UTXO + + + + + + + + 50 + 0 + + + + # of outputs + + + + + + + UTXO Size: + + + + + + + 0 DNET + + + @@ -617,7 +651,7 @@ 0 0 830 - 91 + 95 @@ -1151,7 +1185,7 @@ Custom: - Qt::AlignRight|Qt::AlignTop + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing @@ -1165,7 +1199,8 @@ - + + (Smart fee not initialized yet. This usually takes a few blocks...) diff --git a/src/qt/res/css/drk-1.css b/src/qt/res/css/drk-1.css index 38c43e78056f..f6d6159c213d 100644 --- a/src/qt/res/css/drk-1.css +++ b/src/qt/res/css/drk-1.css @@ -1837,6 +1837,14 @@ QDialog#SendCoinsDialog .QFrame#frameCoinControl .QCheckBox#checkBoxCoinControlC color:#ffffff; } +/*******************************/ +/* SPLIT BLOCK CHECK BOX */ +/*******************************/ + +QDialog#SendCoinsDialog .QFrame#frameCoinControl .QCheckBox#splitBlockCheckBox { +color:#ffffff; +} + /*******************************/ /* CUSTOM CHANGE ADDRESS */ /*******************************/ diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index fc162dae94c4..5cad56222682 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -54,6 +54,10 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) : connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int))); connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &))); + // UTXO Splitter + connect(ui->splitBlockCheckBox, SIGNAL(stateChanged(int)), this, SLOT(splitBlockChecked(int))); + connect(ui->splitBlockLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(splitBlockLineEditChanged(const QString &))); + // DarkNet specific QSettings settings; if (!settings.contains("bUseObfuScation")) @@ -225,6 +229,19 @@ void SendCoinsDialog::on_sendButton_clicked() for(int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast(ui->entries->itemAt(i)->widget()); + + //UTXO splitter - address should be our own + CBitcoinAddress address = entry->getValue().address.toStdString(); + if(!model->isMine(address) && ui->splitBlockCheckBox->checkState() == Qt::Checked) + { + CoinControlDialog::coinControl->fSplitBlock = false; + ui->splitBlockCheckBox->setCheckState(Qt::Unchecked); + QMessageBox::warning(this, tr("Send Coins"), + tr("The split block tool does not work when sending to outside addresses. Try again."), + QMessageBox::Ok, QMessageBox::Ok); + return; + } + if(entry) { if(entry->validate()) @@ -243,6 +260,22 @@ void SendCoinsDialog::on_sendButton_clicked() return; } + //set split block in model + CoinControlDialog::coinControl->fSplitBlock = ui->splitBlockCheckBox->checkState() == Qt::Checked; + + if (ui->entries->count() > 1 && ui->splitBlockCheckBox->checkState() == Qt::Checked) + { + CoinControlDialog::coinControl->fSplitBlock = false; + ui->splitBlockCheckBox->setCheckState(Qt::Unchecked); + QMessageBox::warning(this, tr("Send Coins"), + tr("The split block tool does not work with multiple addresses. Try again."), + QMessageBox::Ok, QMessageBox::Ok); + return; + } + + if (CoinControlDialog::coinControl->fSplitBlock) + CoinControlDialog::coinControl->nSplitBlock = int(ui->splitBlockLineEdit->text().toInt()); + QString strFunds = tr("using") + " " + tr("anonymous funds") + ""; QString strFee = ""; recipients[0].inputType = ONLY_DENOMINATED; @@ -305,6 +338,11 @@ void SendCoinsDialog::on_sendButton_clicked() recipientElement = tr("%1 to %2").arg(amount, address); } + if(fSplitBlock) + { + recipientElement.append(tr(" split into %1 outputs using the UTXO splitter.").arg(CoinControlDialog::coinControl->nSplitBlock)); + } + formatted.append(recipientElement); } @@ -755,6 +793,32 @@ void SendCoinsDialog::updateSmartFeeLabel() updateFeeMinimizedLabel(); } +// UTXO splitter +void SendCoinsDialog::splitBlockChecked(int state) +{ + if (model) + { + CoinControlDialog::coinControl->fSplitBlock = (state == Qt::Checked); + fSplitBlock = (state == Qt::Checked); + ui->splitBlockLineEdit->setEnabled((state == Qt::Checked)); + ui->labelBlockSizeText->setEnabled((state == Qt::Checked)); + ui->labelBlockSize->setEnabled((state == Qt::Checked)); + coinControlUpdateLabels(); + } +} + +//UTXO splitter +void SendCoinsDialog::splitBlockLineEditChanged(const QString & text) +{ + double nAfterFee = ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace("~", "").toDouble(); + double nSize = nAfterFee; + if (nAfterFee > 0 && text.toDouble() > 0) + nSize = nAfterFee / text.toDouble(); + ui->labelBlockSize->setText(QString::number(nSize)); + CoinControlDialog::nSplitBlockDummy = text.toInt(); + coinControlUpdateLabels(); +} + // Coin Control: copy label "Quantity" to clipboard void SendCoinsDialog::coinControlClipboardQuantity() { diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 565c7e9c7758..557172cc949b 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -44,6 +44,7 @@ class SendCoinsDialog : public QDialog void setAddress(const QString &address); void pasteEntry(const SendCoinsRecipient &rv); bool handlePaymentRequest(const SendCoinsRecipient &recipient); + bool fSplitBlock; public slots: void clear(); @@ -89,6 +90,8 @@ private slots: void coinControlClipboardPriority(); void coinControlClipboardLowOutput(); void coinControlClipboardChange(); + void splitBlockChecked(int); + void splitBlockLineEditChanged(const QString & text); void setMinimumFee(); void updateFeeSectionControls(); void updateMinFeeLabel(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 08129353ce6b..cfb8d17899c3 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -719,3 +719,8 @@ bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t else return wallet->AddDestData(dest, key, sRequest); } + +bool WalletModel::isMine(CBitcoinAddress address) +{ + return IsMine(*wallet, address.Get()); +} diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 886137bb504a..41e4b298a263 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -196,6 +196,7 @@ class WalletModel : public QObject UnlockContext requestUnlock(bool relock=false); bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; + bool isMine(CBitcoinAddress address); void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); bool isSpent(const COutPoint& outpoint) const; void listCoins(std::map >& mapCoins) const; diff --git a/src/wallet.cpp b/src/wallet.cpp index 2ec031231be0..b62672fe7495 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -2256,16 +2256,40 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CAmount nTotalValue = nValue + nFeeRet; double dPriority = 0; + // vouts to the payees - BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) + if(coinControl && !coinControl->fSplitBlock) { - CTxOut txout(s.second, s.first); - if (txout.IsDust(::minRelayTxFee)) + BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) { - strFailReason = _("Transaction amount too small"); - return false; + CTxOut txout(s.second, s.first); + if (txout.IsDust(::minRelayTxFee)) + { + strFailReason = _("Transaction amount too small"); + return false; + } + txNew.vout.push_back(txout); + } + } + else //UTXO Splitter Transaction + { + int nSplitBlock = coinControl->nSplitBlock; + if(nSplitBlock < 1) + nSplitBlock = 1; + + BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) + { + for(int i = 0; i < nSplitBlock; i++) + { + if(i == nSplitBlock - 1) + { + uint64_t nRemainder = s.second % nSplitBlock; + txNew.vout.push_back(CTxOut((s.second / nSplitBlock) + nRemainder, s.first)); + } + else + txNew.vout.push_back(CTxOut(s.second / nSplitBlock, s.first)); + } } - txNew.vout.push_back(txout); } // Choose coins to use