diff --git a/doc/release-notes.md b/doc/release-notes.md
index 180b2fe90..d18af8713 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -1,7 +1,8 @@
Bitcoin ABC version 0.17.2 is now available from:
This release includes the following features and fixes:
- Remove deprecated `estimatepriority` RPC.
- Remove deprecated `estimatesmartpriority` RPC.
+ - Remove support for `-sendfreetransactions`.
diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp
index 4b8360e6c..f5c237ba5 100644
--- a/src/qt/coincontroldialog.cpp
+++ b/src/qt/coincontroldialog.cpp
@@ -1,850 +1,844 @@
// 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 "coincontroldialog.h"
#include "ui_coincontroldialog.h"
#include "addresstablemodel.h"
#include "bitcoinunits.h"
#include "guiutil.h"
#include "optionsmodel.h"
#include "platformstyle.h"
#include "txmempool.h"
#include "walletmodel.h"
#include "dstencode.h"
#include "init.h"
#include "policy/policy.h"
#include "validation.h" // For mempool
#include "wallet/coincontrol.h"
#include "wallet/wallet.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
QList CoinControlDialog::payAmounts;
CCoinControl *CoinControlDialog::coinControl = new CCoinControl();
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(const PlatformStyle *_platformStyle,
QWidget *parent)
: QDialog(parent), ui(new Ui::CoinControlDialog), model(0),
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, SIGNAL(customContextMenuRequested(QPoint)), this,
SLOT(showMenu(QPoint)));
connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress()));
connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel()));
connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount()));
connect(copyTransactionHashAction, SIGNAL(triggered()), this,
SLOT(copyTransactionHash()));
connect(lockAction, SIGNAL(triggered()), this, SLOT(lockCoin()));
connect(unlockAction, SIGNAL(triggered()), this, SLOT(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, SIGNAL(triggered()), this,
SLOT(clipboardQuantity()));
connect(clipboardAmountAction, SIGNAL(triggered()), this,
SLOT(clipboardAmount()));
connect(clipboardFeeAction, SIGNAL(triggered()), this,
SLOT(clipboardFee()));
connect(clipboardAfterFeeAction, SIGNAL(triggered()), this,
SLOT(clipboardAfterFee()));
connect(clipboardBytesAction, SIGNAL(triggered()), this,
SLOT(clipboardBytes()));
connect(clipboardLowOutputAction, SIGNAL(triggered()), this,
SLOT(clipboardLowOutput()));
connect(clipboardChangeAction, SIGNAL(triggered()), this,
SLOT(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, SIGNAL(toggled(bool)), this,
SLOT(radioTreeMode(bool)));
connect(ui->radioListMode, SIGNAL(toggled(bool)), this,
SLOT(radioListMode(bool)));
// click on checkbox
connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem *, int)), this,
SLOT(viewItemChanged(QTreeWidgetItem *, int)));
// click on header
#if QT_VERSION < 0x050000
ui->treeWidget->header()->setClickable(true);
#else
ui->treeWidget->header()->setSectionsClickable(true);
#endif
connect(ui->treeWidget->header(), SIGNAL(sectionClicked(int)), this,
SLOT(headerSectionClicked(int)));
// ok button
connect(ui->buttonBox, SIGNAL(clicked(QAbstractButton *)), this,
SLOT(buttonBoxClicked(QAbstractButton *)));
// (un)select all
connect(ui->pushButtonSelectAll, SIGNAL(clicked()), this,
SLOT(buttonSelectAllClicked()));
// change coin control first column label due Qt4 bug.
// see https://github.com/bitcoin/bitcoin/issues/5716
ui->treeWidget->headerItem()->setText(COLUMN_CHECKBOX, QString());
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);
// store transaction hash in this column, but don't show it
ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true);
// store vout index in this column, but don't show it
ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true);
// 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(),
((Qt::SortOrder)settings.value("nCoinControlSortOrder").toInt()));
}
CoinControlDialog::~CoinControlDialog() {
QSettings settings;
settings.setValue("nCoinControlMode", ui->radioListMode->isChecked());
settings.setValue("nCoinControlSortColumn", sortColumn);
settings.setValue("nCoinControlSortOrder", (int)sortOrder);
delete ui;
}
void CoinControlDialog::setModel(WalletModel *_model) {
this->model = _model;
if (_model && _model->getOptionsModel() && _model->getAddressTableModel()) {
updateView();
updateLabelLocked();
CoinControlDialog::updateLabels(_model, this);
}
}
// 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
coinControl->UnSelectAll();
}
CoinControlDialog::updateLabels(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->text(COLUMN_TXHASH).length() == 64) {
// transaction hash is 64 characters (this means its a child node,
// so its not a parent node in tree mode)
copyTransactionHashAction->setEnabled(true);
if (model->isLockedCoin(
uint256S(item->text(COLUMN_TXHASH).toStdString()),
item->text(COLUMN_VOUT_INDEX).toUInt())) {
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->text(COLUMN_TXHASH));
}
// context menu action: lock coin
void CoinControlDialog::lockCoin() {
if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked)
contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
COutPoint outpt(
uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()),
contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt());
model->lockCoin(outpt);
contextMenuItem->setDisabled(true);
contextMenuItem->setIcon(
COLUMN_CHECKBOX, platformStyle->SingleColorIcon(":/icons/lock_closed"));
updateLabelLocked();
}
// context menu action: unlock coin
void CoinControlDialog::unlockCoin() {
COutPoint outpt(
uint256S(contextMenuItem->text(COLUMN_TXHASH).toStdString()),
contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt());
model->unlockCoin(outpt);
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 its a child node, so its
// not a parent node in tree mode)
if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) {
COutPoint outpt(uint256S(item->text(COLUMN_TXHASH).toStdString()),
item->text(COLUMN_VOUT_INDEX).toUInt());
if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) {
coinControl->UnSelect(outpt);
} else if (item->isDisabled()) {
// locked (this happens if "check all" through parent node)
item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
} else {
coinControl->Select(outpt);
}
// selection changed -> update labels
if (ui->treeWidget->isEnabled()) {
// do not update on every click for (un)select all
CoinControlDialog::updateLabels(model, this);
}
}
// TODO: Remove this temporary qt5 fix after Qt5.3 and Qt5.4 are no longer used.
// Fixed in Qt5.5 and above: https://bugreports.qt.io/browse/QTBUG-43473
#if QT_VERSION >= 0x050000
else if (column == COLUMN_CHECKBOX && item->childCount() > 0) {
if (item->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked &&
item->child(0)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked)
item->setCheckState(COLUMN_CHECKBOX, Qt::Checked);
}
#endif
}
// shows count of locked unspent outputs
void CoinControlDialog::updateLabelLocked() {
std::vector vOutpts;
model->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(WalletModel *model, QDialog *dialog) {
if (!model) return;
// nPayAmount
Amount nPayAmount(0);
bool fDust = false;
CMutableTransaction txDummy;
for (const Amount amount : CoinControlDialog::payAmounts) {
nPayAmount += amount;
if (amount > Amount(0)) {
CTxOut txout(Amount(amount), (CScript)std::vector(24, 0));
txDummy.vout.push_back(txout);
if (txout.IsDust(dustRelayFee)) fDust = true;
}
}
Amount nAmount(0);
Amount nPayFee(0);
Amount nAfterFee(0);
Amount nChange(0);
unsigned int nBytes = 0;
unsigned int nBytesInputs = 0;
double dPriority = 0;
double dPriorityInputs = 0;
unsigned int nQuantity = 0;
int nQuantityUncompressed = 0;
bool fAllowFree = false;
std::vector vCoinControl;
std::vector vOutputs;
coinControl->ListSelected(vCoinControl);
model->getOutputs(vCoinControl, vOutputs);
for (const COutput &out : vOutputs) {
// unselect already spent, very unlikely scenario, this could happen
// when selected are spent elsewhere, like rpc or another computer
uint256 txhash = out.tx->GetId();
COutPoint outpt(txhash, out.i);
if (model->isSpent(outpt)) {
coinControl->UnSelect(outpt);
continue;
}
// Quantity
nQuantity++;
// Amount
nAmount += out.tx->tx->vout[out.i].nValue;
// Priority
dPriorityInputs +=
(double)out.tx->tx->vout[out.i].nValue.GetSatoshis() *
(out.nDepth + 1);
// Bytes
CTxDestination address;
if (ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address)) {
CPubKey pubkey;
CKeyID *keyid = boost::get(&address);
if (keyid && model->getPubKey(*keyid, pubkey)) {
nBytesInputs += (pubkey.IsCompressed() ? 148 : 180);
if (!pubkey.IsCompressed()) nQuantityUncompressed++;
} 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(0)) {
nBytes -= 34;
}
}
// Fee
nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
if (nPayFee > Amount(0) && coinControl->nMinimumTotalFee > nPayFee) {
nPayFee = coinControl->nMinimumTotalFee;
}
// Allow free? (require at least hard-coded threshold and default to
// that if no estimate)
double mempoolEstimatePriority =
mempool.estimateSmartPriority(nTxConfirmTarget);
// 29 = 180 - 151 (uncompressed public keys are over the limit. max 151
// bytes of the input are ignored for priority)
dPriority = dPriorityInputs /
(nBytes - nBytesInputs + (nQuantityUncompressed * 29));
double dPriorityNeeded =
std::max(mempoolEstimatePriority, AllowFreeThreshold());
fAllowFree = (dPriority >= dPriorityNeeded);
- if (fSendFreeTransactions) {
- if (fAllowFree && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) {
- nPayFee = Amount(0);
- }
- }
-
if (nPayAmount > Amount(0)) {
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(0) && nChange < MIN_CHANGE) {
CTxOut txout(nChange, (CScript)std::vector(24, 0));
if (txout.IsDust(dustRelayFee)) {
// dust-change will be raised until no dust
if (CoinControlDialog::fSubtractFeeFromAmount) {
nChange = txout.GetDustThreshold(dustRelayFee);
} else {
nPayFee += nChange;
nChange = Amount(0);
}
}
}
if (nChange == Amount(0) &&
!CoinControlDialog::fSubtractFeeFromAmount) {
nBytes -= 34;
}
}
// after fee
nAfterFee = std::max(nAmount - nPayFee, Amount(0));
}
// 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(0));
dialog->findChild("labelCoinControlLowOutput")
->setEnabled(nPayAmount > Amount(0));
dialog->findChild("labelCoinControlChangeText")
->setEnabled(nPayAmount > Amount(0));
dialog->findChild("labelCoinControlChange")
->setEnabled(nPayAmount > Amount(0));
// 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(0) && (coinControl->nMinimumTotalFee < nPayFee)) {
l3->setText(ASYMP_UTF8 + l3->text());
l4->setText(ASYMP_UTF8 + l4->text());
if (nChange > Amount(0) && !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;
if (payTxFee.GetFeePerK() > Amount(0)) {
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000),
payTxFee.GetFeePerK())
.GetSatoshis() /
1000;
} else {
dFeeVary = (double)std::max(
CWallet::GetRequiredFee(1000),
mempool.estimateSmartFee(nTxConfirmTarget).GetFeePerK())
.GetSatoshis() /
1000;
}
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(0));
}
}
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();
std::map> mapCoins;
model->listCoins(mapCoins);
for (const std::pair> &coins : mapCoins) {
CCoinControlWidgetItem *itemWalletAddress =
new CCoinControlWidgetItem();
itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
QString sWalletAddress = coins.first;
QString sWalletLabel =
model->getAddressTableModel()->labelForAddress(sWalletAddress);
if (sWalletLabel.isEmpty()) {
sWalletLabel = tr("(no label)");
}
if (treeMode) {
// wallet address
ui->treeWidget->addTopLevelItem(itemWalletAddress);
itemWalletAddress->setFlags(flgTristate);
itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
// label
itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel);
// address
itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress);
}
Amount nSum(0);
int nChildren = 0;
for (const COutput &out : coins.second) {
nSum += out.tx->tx->vout[out.i].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.tx->tx->vout[out.i].scriptPubKey,
outputAddress)) {
sAddress =
QString::fromStdString(EncodeDestination(outputAddress));
// 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.tx->tx->vout[out.i].nValue));
// padding so that sorting works correctly
itemOutput->setData(
COLUMN_AMOUNT, Qt::UserRole,
QVariant(
(qlonglong)out.tx->tx->vout[out.i].nValue.GetSatoshis()));
// date
itemOutput->setText(COLUMN_DATE,
GUIUtil::dateTimeStr(out.tx->GetTxTime()));
itemOutput->setData(COLUMN_DATE, Qt::UserRole,
QVariant((qlonglong)out.tx->GetTxTime()));
// confirmations
itemOutput->setText(COLUMN_CONFIRMATIONS,
QString::number(out.nDepth));
itemOutput->setData(COLUMN_CONFIRMATIONS, Qt::UserRole,
QVariant((qlonglong)out.nDepth));
// transaction hash
uint256 txhash = out.tx->GetId();
itemOutput->setText(COLUMN_TXHASH,
QString::fromStdString(txhash.GetHex()));
// vout index
itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(out.i));
// disable locked coins
if (model->isLockedCoin(txhash, out.i)) {
COutPoint outpt(txhash, out.i);
// just to be sure
coinControl->UnSelect(outpt);
itemOutput->setDisabled(true);
itemOutput->setIcon(
COLUMN_CHECKBOX,
platformStyle->SingleColorIcon(":/icons/lock_closed"));
}
// set checkbox
if (coinControl->IsSelected(COutPoint(txhash, out.i))) {
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.GetSatoshis()));
}
}
// 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/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 922c2f893..0a665f881 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,4571 +1,4555 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "wallet/wallet.h"
#include "chain.h"
#include "checkpoints.h"
#include "config.h"
#include "consensus/consensus.h"
#include "consensus/validation.h"
#include "dstencode.h"
#include "fs.h"
#include "init.h"
#include "key.h"
#include "keystore.h"
#include "net.h"
#include "policy/policy.h"
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "scheduler.h"
#include "script/script.h"
#include "script/sighashtype.h"
#include "script/sign.h"
#include "timedata.h"
#include "txmempool.h"
#include "ui_interface.h"
#include "util.h"
#include "utilmoneystr.h"
#include "validation.h"
#include "wallet/coincontrol.h"
#include "wallet/finaltx.h"
#include
#include
#include
std::vector vpwallets;
/** Transaction fee set by the user */
CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET;
bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE;
-bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS;
const char *DEFAULT_WALLET_DAT = "wallet.dat";
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
/**
* Fees smaller than this (in satoshi) are considered zero fee (for transaction
* creation)
* Override with -mintxfee
*/
CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE);
/**
* If fee estimation does not have enough data to provide estimates, use this
* fee instead. Has no effect if not using fee estimation.
* Override with -fallbackfee
*/
CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE);
const uint256 CMerkleTx::ABANDON_HASH(uint256S(
"0000000000000000000000000000000000000000000000000000000000000001"));
/** @defgroup mapWallet
*
* @{
*/
struct CompareValueOnly {
bool operator()(
const std::pair> &t1,
const std::pair> &t2)
const {
return t1.first < t2.first;
}
};
std::string COutput::ToString() const {
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetId().ToString(), i,
nDepth, FormatMoney(tx->tx->vout[i].nValue));
}
class CAffectedKeysVisitor : public boost::static_visitor {
private:
const CKeyStore &keystore;
std::vector &vKeys;
public:
CAffectedKeysVisitor(const CKeyStore &keystoreIn,
std::vector &vKeysIn)
: keystore(keystoreIn), vKeys(vKeysIn) {}
void Process(const CScript &script) {
txnouttype type;
std::vector vDest;
int nRequired;
if (ExtractDestinations(script, type, vDest, nRequired)) {
for (const CTxDestination &dest : vDest) {
boost::apply_visitor(*this, dest);
}
}
}
void operator()(const CKeyID &keyId) {
if (keystore.HaveKey(keyId)) {
vKeys.push_back(keyId);
}
}
void operator()(const CScriptID &scriptId) {
CScript script;
if (keystore.GetCScript(scriptId, script)) {
Process(script);
}
}
void operator()(const CNoDestination &none) {}
};
const CWalletTx *CWallet::GetWalletTx(const uint256 &hash) const {
LOCK(cs_wallet);
std::map::const_iterator it = mapWallet.find(hash);
if (it == mapWallet.end()) {
return nullptr;
}
return &(it->second);
}
CPubKey CWallet::GenerateNewKey(CWalletDB &walletdb, bool internal) {
// mapKeyMetadata
AssertLockHeld(cs_wallet);
// default to compressed public keys if we want 0.6.0 wallets
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY);
CKey secret;
// Create new metadata
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
// use HD key derivation if HD was enabled during wallet creation
if (IsHDEnabled()) {
DeriveNewChildKey(
walletdb, metadata, secret,
(CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
} else {
secret.MakeNewKey(fCompressed);
}
// Compressed public keys were introduced in version 0.6.0
if (fCompressed) {
SetMinVersion(FEATURE_COMPRPUBKEY);
}
CPubKey pubkey = secret.GetPubKey();
assert(secret.VerifyPubKey(pubkey));
mapKeyMetadata[pubkey.GetID()] = metadata;
UpdateTimeFirstKey(nCreationTime);
if (!AddKeyPubKeyWithDB(walletdb, secret, pubkey)) {
throw std::runtime_error(std::string(__func__) + ": AddKey failed");
}
return pubkey;
}
void CWallet::DeriveNewChildKey(CWalletDB &walletdb, CKeyMetadata &metadata,
CKey &secret, bool internal) {
// for now we use a fixed keypath scheme of m/0'/0'/k
// master key seed (256bit)
CKey key;
// hd master key
CExtKey masterKey;
// key at m/0'
CExtKey accountKey;
// key at m/0'/0' (external) or m/0'/1' (internal)
CExtKey chainChildKey;
// key at m/0'/0'/'
CExtKey childKey;
// try to get the master key
if (!GetKey(hdChain.masterKeyID, key)) {
throw std::runtime_error(std::string(__func__) +
": Master key not found");
}
masterKey.SetMaster(key.begin(), key.size());
// derive m/0'
// use hardened derivation (child keys >= 0x80000000 are hardened after
// bip32)
masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
// derive m/0'/0' (external chain) OR m/0'/1' (internal chain)
assert(internal ? CanSupportFeature(FEATURE_HD_SPLIT) : true);
accountKey.Derive(chainChildKey,
BIP32_HARDENED_KEY_LIMIT + (internal ? 1 : 0));
// derive child key at next index, skip keys already known to the wallet
do {
// always derive hardened keys
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened
// child-index-range
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
if (internal) {
chainChildKey.Derive(childKey,
hdChain.nInternalChainCounter |
BIP32_HARDENED_KEY_LIMIT);
metadata.hdKeypath = "m/0'/1'/" +
std::to_string(hdChain.nInternalChainCounter) +
"'";
hdChain.nInternalChainCounter++;
} else {
chainChildKey.Derive(childKey,
hdChain.nExternalChainCounter |
BIP32_HARDENED_KEY_LIMIT);
metadata.hdKeypath = "m/0'/0'/" +
std::to_string(hdChain.nExternalChainCounter) +
"'";
hdChain.nExternalChainCounter++;
}
} while (HaveKey(childKey.key.GetPubKey().GetID()));
secret = childKey.key;
metadata.hdMasterKeyID = hdChain.masterKeyID;
// update the chain model in the database
if (!walletdb.WriteHDChain(hdChain)) {
throw std::runtime_error(std::string(__func__) +
": Writing HD chain model failed");
}
}
bool CWallet::AddKeyPubKeyWithDB(CWalletDB &walletdb, const CKey &secret,
const CPubKey &pubkey) {
// mapKeyMetadata
AssertLockHeld(cs_wallet);
// CCryptoKeyStore has no concept of wallet databases, but calls
// AddCryptedKey
// which is overridden below. To avoid flushes, the database handle is
// tunneled through to it.
bool needsDB = !pwalletdbEncryption;
if (needsDB) {
pwalletdbEncryption = &walletdb;
}
if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) {
if (needsDB) {
pwalletdbEncryption = nullptr;
}
return false;
}
if (needsDB) {
pwalletdbEncryption = nullptr;
}
// Check if we need to remove from watch-only.
CScript script;
script = GetScriptForDestination(pubkey.GetID());
if (HaveWatchOnly(script)) {
RemoveWatchOnly(script);
}
script = GetScriptForRawPubKey(pubkey);
if (HaveWatchOnly(script)) {
RemoveWatchOnly(script);
}
if (IsCrypted()) {
return true;
}
return walletdb.WriteKey(pubkey, secret.GetPrivKey(),
mapKeyMetadata[pubkey.GetID()]);
}
bool CWallet::AddKeyPubKey(const CKey &secret, const CPubKey &pubkey) {
CWalletDB walletdb(*dbw);
return CWallet::AddKeyPubKeyWithDB(walletdb, secret, pubkey);
}
bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
const std::vector &vchCryptedSecret) {
if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) {
return false;
}
LOCK(cs_wallet);
if (pwalletdbEncryption) {
return pwalletdbEncryption->WriteCryptedKey(
vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]);
}
return CWalletDB(*dbw).WriteCryptedKey(vchPubKey, vchCryptedSecret,
mapKeyMetadata[vchPubKey.GetID()]);
}
bool CWallet::LoadKeyMetadata(const CTxDestination &keyID,
const CKeyMetadata &meta) {
// mapKeyMetadata
AssertLockHeld(cs_wallet);
UpdateTimeFirstKey(meta.nCreateTime);
mapKeyMetadata[keyID] = meta;
return true;
}
bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey,
const std::vector &vchCryptedSecret) {
return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret);
}
void CWallet::UpdateTimeFirstKey(int64_t nCreateTime) {
AssertLockHeld(cs_wallet);
if (nCreateTime <= 1) {
// Cannot determine birthday information, so set the wallet birthday to
// the beginning of time.
nTimeFirstKey = 1;
} else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) {
nTimeFirstKey = nCreateTime;
}
}
bool CWallet::AddCScript(const CScript &redeemScript) {
if (!CCryptoKeyStore::AddCScript(redeemScript)) {
return false;
}
return CWalletDB(*dbw).WriteCScript(Hash160(redeemScript), redeemScript);
}
bool CWallet::LoadCScript(const CScript &redeemScript) {
/**
* A sanity check was added in pull #3843 to avoid adding redeemScripts that
* never can be redeemed. However, old wallets may still contain these. Do
* not add them to the wallet and warn.
*/
if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) {
std::string strAddr = EncodeDestination(CScriptID(redeemScript));
LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i "
"which exceeds maximum size %i thus can never be redeemed. "
"Do not use address %s.\n",
__func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE,
strAddr);
return true;
}
return CCryptoKeyStore::AddCScript(redeemScript);
}
bool CWallet::AddWatchOnly(const CScript &dest) {
if (!CCryptoKeyStore::AddWatchOnly(dest)) {
return false;
}
const CKeyMetadata &meta = mapKeyMetadata[CScriptID(dest)];
UpdateTimeFirstKey(meta.nCreateTime);
NotifyWatchonlyChanged(true);
return CWalletDB(*dbw).WriteWatchOnly(dest, meta);
}
bool CWallet::AddWatchOnly(const CScript &dest, int64_t nCreateTime) {
mapKeyMetadata[CScriptID(dest)].nCreateTime = nCreateTime;
return AddWatchOnly(dest);
}
bool CWallet::RemoveWatchOnly(const CScript &dest) {
AssertLockHeld(cs_wallet);
if (!CCryptoKeyStore::RemoveWatchOnly(dest)) {
return false;
}
if (!HaveWatchOnly()) {
NotifyWatchonlyChanged(false);
}
return CWalletDB(*dbw).EraseWatchOnly(dest);
}
bool CWallet::LoadWatchOnly(const CScript &dest) {
return CCryptoKeyStore::AddWatchOnly(dest);
}
bool CWallet::Unlock(const SecureString &strWalletPassphrase) {
CCrypter crypter;
CKeyingMaterial vMasterKey;
LOCK(cs_wallet);
for (const MasterKeyMap::value_type &pMasterKey : mapMasterKeys) {
if (!crypter.SetKeyFromPassphrase(
strWalletPassphrase, pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod)) {
return false;
}
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) {
// try another master key
continue;
}
if (CCryptoKeyStore::Unlock(vMasterKey)) {
return true;
}
}
return false;
}
bool CWallet::ChangeWalletPassphrase(
const SecureString &strOldWalletPassphrase,
const SecureString &strNewWalletPassphrase) {
bool fWasLocked = IsLocked();
LOCK(cs_wallet);
Lock();
CCrypter crypter;
CKeyingMaterial vMasterKey;
for (MasterKeyMap::value_type &pMasterKey : mapMasterKeys) {
if (!crypter.SetKeyFromPassphrase(
strOldWalletPassphrase, pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod)) {
return false;
}
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) {
return false;
}
if (CCryptoKeyStore::Unlock(vMasterKey)) {
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase,
pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations =
pMasterKey.second.nDeriveIterations *
(100 / ((double)(GetTimeMillis() - nStartTime)));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase,
pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations =
(pMasterKey.second.nDeriveIterations +
pMasterKey.second.nDeriveIterations * 100 /
double(GetTimeMillis() - nStartTime)) /
2;
if (pMasterKey.second.nDeriveIterations < 25000) {
pMasterKey.second.nDeriveIterations = 25000;
}
LogPrintf(
"Wallet passphrase changed to an nDeriveIterations of %i\n",
pMasterKey.second.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(
strNewWalletPassphrase, pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod)) {
return false;
}
if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey)) {
return false;
}
CWalletDB(*dbw).WriteMasterKey(pMasterKey.first, pMasterKey.second);
if (fWasLocked) {
Lock();
}
return true;
}
}
return false;
}
void CWallet::SetBestChain(const CBlockLocator &loc) {
CWalletDB walletdb(*dbw);
walletdb.WriteBestBlock(loc);
}
bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB *pwalletdbIn,
bool fExplicit) {
// nWalletVersion
LOCK(cs_wallet);
if (nWalletVersion >= nVersion) {
return true;
}
// When doing an explicit upgrade, if we pass the max version permitted,
// upgrade all the way.
if (fExplicit && nVersion > nWalletMaxVersion) {
nVersion = FEATURE_LATEST;
}
nWalletVersion = nVersion;
if (nVersion > nWalletMaxVersion) {
nWalletMaxVersion = nVersion;
}
CWalletDB *pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(*dbw);
if (nWalletVersion > 40000) {
pwalletdb->WriteMinVersion(nWalletVersion);
}
if (!pwalletdbIn) {
delete pwalletdb;
}
return true;
}
bool CWallet::SetMaxVersion(int nVersion) {
// nWalletVersion, nWalletMaxVersion
LOCK(cs_wallet);
// Cannot downgrade below current version
if (nWalletVersion > nVersion) {
return false;
}
nWalletMaxVersion = nVersion;
return true;
}
std::set CWallet::GetConflicts(const uint256 &txid) const {
std::set result;
AssertLockHeld(cs_wallet);
std::map::const_iterator it = mapWallet.find(txid);
if (it == mapWallet.end()) {
return result;
}
const CWalletTx &wtx = it->second;
std::pair range;
for (const CTxIn &txin : wtx.tx->vin) {
if (mapTxSpends.count(txin.prevout) <= 1) {
// No conflict if zero or one spends.
continue;
}
range = mapTxSpends.equal_range(txin.prevout);
for (TxSpends::const_iterator _it = range.first; _it != range.second;
++_it) {
result.insert(_it->second);
}
}
return result;
}
bool CWallet::HasWalletSpend(const uint256 &txid) const {
AssertLockHeld(cs_wallet);
auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0));
return (iter != mapTxSpends.end() && iter->first.hash == txid);
}
void CWallet::Flush(bool shutdown) {
dbw->Flush(shutdown);
}
bool CWallet::Verify(const CChainParams &chainParams) {
if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
return true;
}
uiInterface.InitMessage(_("Verifying wallet(s)..."));
// Keep track of each wallet absolute path to detect duplicates.
std::set wallet_paths;
for (const std::string &walletFile : gArgs.GetArgs("-wallet")) {
if (fs::path(walletFile).filename() != walletFile) {
return InitError(
strprintf(_("Error loading wallet %s. -wallet parameter must "
"only specify a filename (not a path)."),
walletFile));
}
if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) {
return InitError(strprintf(_("Error loading wallet %s. Invalid "
"characters in -wallet filename."),
walletFile));
}
fs::path wallet_path = fs::absolute(walletFile, GetDataDir());
if (fs::exists(wallet_path) && (!fs::is_regular_file(wallet_path) ||
fs::is_symlink(wallet_path))) {
return InitError(strprintf(_("Error loading wallet %s. -wallet "
"filename must be a regular file."),
walletFile));
}
if (!wallet_paths.insert(wallet_path).second) {
return InitError(strprintf(_("Error loading wallet %s. Duplicate "
"-wallet filename specified."),
walletFile));
}
std::string strError;
if (!CWalletDB::VerifyEnvironment(walletFile, GetDataDir().string(),
strError)) {
return InitError(strError);
}
if (gArgs.GetBoolArg("-salvagewallet", false)) {
// Recover readable keypairs:
CWallet dummyWallet(chainParams);
std::string backup_filename;
if (!CWalletDB::Recover(walletFile, (void *)&dummyWallet,
CWalletDB::RecoverKeysOnlyFilter,
backup_filename)) {
return false;
}
}
std::string strWarning;
bool dbV = CWalletDB::VerifyDatabaseFile(
walletFile, GetDataDir().string(), strWarning, strError);
if (!strWarning.empty()) {
InitWarning(strWarning);
}
if (!dbV) {
InitError(strError);
return false;
}
}
return true;
}
void CWallet::SyncMetaData(
std::pair range) {
// We want all the wallet transactions in range to have the same metadata as
// the oldest (smallest nOrderPos).
// So: find smallest nOrderPos:
int nMinOrderPos = std::numeric_limits::max();
const CWalletTx *copyFrom = nullptr;
for (TxSpends::iterator it = range.first; it != range.second; ++it) {
const uint256 &hash = it->second;
int n = mapWallet[hash].nOrderPos;
if (n < nMinOrderPos) {
nMinOrderPos = n;
copyFrom = &mapWallet[hash];
}
}
// Now copy data from copyFrom to rest:
for (TxSpends::iterator it = range.first; it != range.second; ++it) {
const uint256 &hash = it->second;
CWalletTx *copyTo = &mapWallet[hash];
if (copyFrom == copyTo) {
continue;
}
if (!copyFrom->IsEquivalentTo(*copyTo)) {
continue;
}
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
// fTimeReceivedIsTxTime not copied on purpose nTimeReceived not copied
// on purpose.
copyTo->nTimeSmart = copyFrom->nTimeSmart;
copyTo->fFromMe = copyFrom->fFromMe;
copyTo->strFromAccount = copyFrom->strFromAccount;
// nOrderPos not copied on purpose cached members not copied on purpose.
}
}
/**
* Outpoint is spent if any non-conflicted transaction, spends it:
*/
bool CWallet::IsSpent(const uint256 &hash, unsigned int n) const {
const COutPoint outpoint(hash, n);
std::pair range;
range = mapTxSpends.equal_range(outpoint);
for (TxSpends::const_iterator it = range.first; it != range.second; ++it) {
const uint256 &wtxid = it->second;
std::map::const_iterator mit =
mapWallet.find(wtxid);
if (mit != mapWallet.end()) {
int depth = mit->second.GetDepthInMainChain();
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) {
// Spent
return true;
}
}
}
return false;
}
void CWallet::AddToSpends(const COutPoint &outpoint, const uint256 &wtxid) {
mapTxSpends.insert(std::make_pair(outpoint, wtxid));
std::pair range;
range = mapTxSpends.equal_range(outpoint);
SyncMetaData(range);
}
void CWallet::AddToSpends(const uint256 &wtxid) {
assert(mapWallet.count(wtxid));
CWalletTx &thisTx = mapWallet[wtxid];
// Coinbases don't spend anything!
if (thisTx.IsCoinBase()) {
return;
}
for (const CTxIn &txin : thisTx.tx->vin) {
AddToSpends(txin.prevout, wtxid);
}
}
bool CWallet::EncryptWallet(const SecureString &strWalletPassphrase) {
if (IsCrypted()) {
return false;
}
CKeyingMaterial vMasterKey;
vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
GetStrongRandBytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
CMasterKey kMasterKey;
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
GetStrongRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
CCrypter crypter;
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000,
kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations =
2500000 / ((double)(GetTimeMillis() - nStartTime));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt,
kMasterKey.nDeriveIterations,
kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations =
(kMasterKey.nDeriveIterations +
kMasterKey.nDeriveIterations * 100 /
((double)(GetTimeMillis() - nStartTime))) /
2;
if (kMasterKey.nDeriveIterations < 25000) {
kMasterKey.nDeriveIterations = 25000;
}
LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n",
kMasterKey.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt,
kMasterKey.nDeriveIterations,
kMasterKey.nDerivationMethod)) {
return false;
}
if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey)) {
return false;
}
{
LOCK(cs_wallet);
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
assert(!pwalletdbEncryption);
pwalletdbEncryption = new CWalletDB(*dbw);
if (!pwalletdbEncryption->TxnBegin()) {
delete pwalletdbEncryption;
pwalletdbEncryption = nullptr;
return false;
}
pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
if (!EncryptKeys(vMasterKey)) {
pwalletdbEncryption->TxnAbort();
delete pwalletdbEncryption;
// We now probably have half of our keys encrypted in memory, and
// half not... die and let the user reload the unencrypted wallet.
assert(false);
}
// Encryption was introduced in version 0.4.0
SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true);
if (!pwalletdbEncryption->TxnCommit()) {
delete pwalletdbEncryption;
// We now have keys encrypted in memory, but not on disk... die to
// avoid confusion and let the user reload the unencrypted wallet.
assert(false);
}
delete pwalletdbEncryption;
pwalletdbEncryption = nullptr;
Lock();
Unlock(strWalletPassphrase);
// If we are using HD, replace the HD master key (seed) with a new one.
if (IsHDEnabled()) {
CKey key;
CPubKey masterPubKey = GenerateNewHDMasterKey();
// preserve the old chains version to not break backward
// compatibility
CHDChain oldChain = GetHDChain();
if (!SetHDMasterKey(masterPubKey, &oldChain)) {
return false;
}
}
NewKeyPool();
Lock();
// Need to completely rewrite the wallet file; if we don't, bdb might
// keep bits of the unencrypted private key in slack space in the
// database file.
dbw->Rewrite();
}
NotifyStatusChanged(this);
return true;
}
DBErrors CWallet::ReorderTransactions() {
LOCK(cs_wallet);
CWalletDB walletdb(*dbw);
// Old wallets didn't have any defined order for transactions. Probably a
// bad idea to change the output of this.
// First: get all CWalletTx and CAccountingEntry into a sorted-by-time
// multimap.
typedef std::pair TxPair;
typedef std::multimap TxItems;
TxItems txByTime;
for (std::map::iterator it = mapWallet.begin();
it != mapWallet.end(); ++it) {
CWalletTx *wtx = &((*it).second);
txByTime.insert(
std::make_pair(wtx->nTimeReceived, TxPair(wtx, nullptr)));
}
std::list acentries;
walletdb.ListAccountCreditDebit("", acentries);
for (CAccountingEntry &entry : acentries) {
txByTime.insert(std::make_pair(entry.nTime, TxPair(nullptr, &entry)));
}
nOrderPosNext = 0;
std::vector nOrderPosOffsets;
for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it) {
CWalletTx *const pwtx = (*it).second.first;
CAccountingEntry *const pacentry = (*it).second.second;
int64_t &nOrderPos =
(pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos;
if (nOrderPos == -1) {
nOrderPos = nOrderPosNext++;
nOrderPosOffsets.push_back(nOrderPos);
if (pwtx) {
if (!walletdb.WriteTx(*pwtx)) {
return DB_LOAD_FAIL;
}
} else if (!walletdb.WriteAccountingEntry(pacentry->nEntryNo,
*pacentry)) {
return DB_LOAD_FAIL;
}
} else {
int64_t nOrderPosOff = 0;
for (const int64_t &nOffsetStart : nOrderPosOffsets) {
if (nOrderPos >= nOffsetStart) {
++nOrderPosOff;
}
}
nOrderPos += nOrderPosOff;
nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
if (!nOrderPosOff) {
continue;
}
// Since we're changing the order, write it back.
if (pwtx) {
if (!walletdb.WriteTx(*pwtx)) {
return DB_LOAD_FAIL;
}
} else if (!walletdb.WriteAccountingEntry(pacentry->nEntryNo,
*pacentry)) {
return DB_LOAD_FAIL;
}
}
}
walletdb.WriteOrderPosNext(nOrderPosNext);
return DB_LOAD_OK;
}
int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb) {
// nOrderPosNext
AssertLockHeld(cs_wallet);
int64_t nRet = nOrderPosNext++;
if (pwalletdb) {
pwalletdb->WriteOrderPosNext(nOrderPosNext);
} else {
CWalletDB(*dbw).WriteOrderPosNext(nOrderPosNext);
}
return nRet;
}
bool CWallet::AccountMove(std::string strFrom, std::string strTo,
const Amount nAmount, std::string strComment) {
CWalletDB walletdb(*dbw);
if (!walletdb.TxnBegin()) {
return false;
}
int64_t nNow = GetAdjustedTime();
// Debit
CAccountingEntry debit;
debit.nOrderPos = IncOrderPosNext(&walletdb);
debit.strAccount = strFrom;
debit.nCreditDebit = -nAmount;
debit.nTime = nNow;
debit.strOtherAccount = strTo;
debit.strComment = strComment;
AddAccountingEntry(debit, &walletdb);
// Credit
CAccountingEntry credit;
credit.nOrderPos = IncOrderPosNext(&walletdb);
credit.strAccount = strTo;
credit.nCreditDebit = nAmount;
credit.nTime = nNow;
credit.strOtherAccount = strFrom;
credit.strComment = strComment;
AddAccountingEntry(credit, &walletdb);
return walletdb.TxnCommit();
}
bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount,
bool bForceNew) {
CWalletDB walletdb(*dbw);
CAccount account;
walletdb.ReadAccount(strAccount, account);
if (!bForceNew) {
if (!account.vchPubKey.IsValid()) {
bForceNew = true;
} else {
// Check if the current key has been used.
CScript scriptPubKey =
GetScriptForDestination(account.vchPubKey.GetID());
for (std::map::iterator it = mapWallet.begin();
it != mapWallet.end() && account.vchPubKey.IsValid(); ++it) {
for (const CTxOut &txout : (*it).second.tx->vout) {
if (txout.scriptPubKey == scriptPubKey) {
bForceNew = true;
break;
}
}
}
}
}
// Generate a new key
if (bForceNew) {
if (!GetKeyFromPool(account.vchPubKey, false)) {
return false;
}
SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive");
walletdb.WriteAccount(strAccount, account);
}
pubKey = account.vchPubKey;
return true;
}
void CWallet::MarkDirty() {
LOCK(cs_wallet);
for (std::pair &item : mapWallet) {
item.second.MarkDirty();
}
}
bool CWallet::AddToWallet(const CWalletTx &wtxIn, bool fFlushOnClose) {
LOCK(cs_wallet);
CWalletDB walletdb(*dbw, "r+", fFlushOnClose);
uint256 hash = wtxIn.GetId();
// Inserts only if not already there, returns tx inserted or tx found.
std::pair::iterator, bool> ret =
mapWallet.insert(std::make_pair(hash, wtxIn));
CWalletTx &wtx = (*ret.first).second;
wtx.BindWallet(this);
bool fInsertedNew = ret.second;
if (fInsertedNew) {
wtx.nTimeReceived = GetAdjustedTime();
wtx.nOrderPos = IncOrderPosNext(&walletdb);
wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr)));
wtx.nTimeSmart = ComputeTimeSmart(wtx);
AddToSpends(hash);
}
bool fUpdated = false;
if (!fInsertedNew) {
// Merge
if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock) {
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
}
// If no longer abandoned, update
if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned()) {
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
}
if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex)) {
wtx.nIndex = wtxIn.nIndex;
fUpdated = true;
}
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) {
wtx.fFromMe = wtxIn.fFromMe;
fUpdated = true;
}
}
//// debug print
LogPrintf("AddToWallet %s %s%s\n", wtxIn.GetId().ToString(),
(fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
// Write to disk
if ((fInsertedNew || fUpdated) && !walletdb.WriteTx(wtx)) {
return false;
}
// Break debit/credit balance caches:
wtx.MarkDirty();
// Notify UI of new or updated transaction.
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
// Notify an external script when a wallet transaction comes in or is
// updated.
std::string strCmd = gArgs.GetArg("-walletnotify", "");
if (!strCmd.empty()) {
boost::replace_all(strCmd, "%s", wtxIn.GetId().GetHex());
// Thread runs free.
boost::thread t(runCommand, strCmd);
}
return true;
}
bool CWallet::LoadToWallet(const CWalletTx &wtxIn) {
uint256 txid = wtxIn.GetId();
mapWallet[txid] = wtxIn;
CWalletTx &wtx = mapWallet[txid];
wtx.BindWallet(this);
wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr)));
AddToSpends(txid);
for (const CTxIn &txin : wtx.tx->vin) {
if (mapWallet.count(txin.prevout.hash)) {
CWalletTx &prevtx = mapWallet[txin.prevout.hash];
if (prevtx.nIndex == -1 && !prevtx.hashUnset()) {
MarkConflicted(prevtx.hashBlock, wtx.GetId());
}
}
}
return true;
}
/**
* Add a transaction to the wallet, or update it. pIndex and posInBlock should
* be set when the transaction was known to be included in a block. When pIndex
* == nullptr, then wallet state is not updated in AddToWallet, but
* notifications happen and cached balances are marked dirty.
*
* If fUpdate is true, existing transactions will be updated.
* TODO: One exception to this is that the abandoned state is cleared under the
* assumption that any further notification of a transaction that was considered
* abandoned is an indication that it is not safe to be considered abandoned.
* Abandoned state should probably be more carefuly tracked via different
* posInBlock signals or by checking mempool presence when necessary.
*/
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef &ptx,
const CBlockIndex *pIndex,
int posInBlock, bool fUpdate) {
const CTransaction &tx = *ptx;
AssertLockHeld(cs_wallet);
if (pIndex != nullptr) {
for (const CTxIn &txin : tx.vin) {
std::pair
range = mapTxSpends.equal_range(txin.prevout);
while (range.first != range.second) {
if (range.first->second != tx.GetId()) {
LogPrintf("Transaction %s (in block %s) conflicts with "
"wallet transaction %s (both spend %s:%i)\n",
tx.GetId().ToString(),
pIndex->GetBlockHash().ToString(),
range.first->second.ToString(),
range.first->first.hash.ToString(),
range.first->first.n);
MarkConflicted(pIndex->GetBlockHash(), range.first->second);
}
range.first++;
}
}
}
bool fExisted = mapWallet.count(tx.GetId()) != 0;
if (fExisted && !fUpdate) {
return false;
}
if (fExisted || IsMine(tx) || IsFromMe(tx)) {
/**
* Check if any keys in the wallet keypool that were supposed to be
* unused have appeared in a new transaction. If so, remove those keys
* from the keypool. This can happen when restoring an old wallet backup
* that does not contain the mostly recently created transactions from
* newer versions of the wallet.
*/
// loop though all outputs
for (const CTxOut &txout : tx.vout) {
// extract addresses and check if they match with an unused keypool
// key
std::vector vAffected;
CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
for (const CKeyID &keyid : vAffected) {
std::map::const_iterator mi =
m_pool_key_to_index.find(keyid);
if (mi != m_pool_key_to_index.end()) {
LogPrintf("%s: Detected a used keypool key, mark all "
"keypool key up to this key as used\n",
__func__);
MarkReserveKeysAsUsed(mi->second);
if (!TopUpKeyPool()) {
LogPrintf(
"%s: Topping up keypool failed (locked wallet)\n",
__func__);
}
}
}
}
CWalletTx wtx(this, ptx);
// Get merkle branch if transaction was found in a block
if (pIndex != nullptr) {
wtx.SetMerkleBranch(pIndex, posInBlock);
}
return AddToWallet(wtx, false);
}
return false;
}
bool CWallet::AbandonTransaction(const uint256 &hashTx) {
LOCK2(cs_main, cs_wallet);
CWalletDB walletdb(*dbw, "r+");
std::set todo;
std::set done;
// Can't mark abandoned if confirmed or in mempool.
assert(mapWallet.count(hashTx));
CWalletTx &origtx = mapWallet[hashTx];
if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) {
return false;
}
todo.insert(hashTx);
while (!todo.empty()) {
uint256 now = *todo.begin();
todo.erase(now);
done.insert(now);
assert(mapWallet.count(now));
CWalletTx &wtx = mapWallet[now];
int currentconfirm = wtx.GetDepthInMainChain();
// If the orig tx was not in block, none of its spends can be.
assert(currentconfirm <= 0);
// If (currentconfirm < 0) {Tx and spends are already conflicted, no
// need to abandon}
if (currentconfirm == 0 && !wtx.isAbandoned()) {
// If the orig tx was not in block/mempool, none of its spends can
// be in mempool.
assert(!wtx.InMempool());
wtx.nIndex = -1;
wtx.setAbandoned();
wtx.MarkDirty();
walletdb.WriteTx(wtx);
NotifyTransactionChanged(this, wtx.GetId(), CT_UPDATED);
// Iterate over all its outputs, and mark transactions in the wallet
// that spend them abandoned too.
TxSpends::const_iterator iter =
mapTxSpends.lower_bound(COutPoint(hashTx, 0));
while (iter != mapTxSpends.end() && iter->first.hash == now) {
if (!done.count(iter->second)) {
todo.insert(iter->second);
}
iter++;
}
// If a transaction changes 'conflicted' state, that changes the
// balance available of the outputs it spends. So force those to be
// recomputed.
for (const CTxIn &txin : wtx.tx->vin) {
if (mapWallet.count(txin.prevout.hash)) {
mapWallet[txin.prevout.hash].MarkDirty();
}
}
}
}
return true;
}
void CWallet::MarkConflicted(const uint256 &hashBlock, const uint256 &hashTx) {
LOCK2(cs_main, cs_wallet);
int conflictconfirms = 0;
if (mapBlockIndex.count(hashBlock)) {
CBlockIndex *pindex = mapBlockIndex[hashBlock];
if (chainActive.Contains(pindex)) {
conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1);
}
}
// If number of conflict confirms cannot be determined, this means that the
// block is still unknown or not yet part of the main chain, for example
// when loading the wallet during a reindex. Do nothing in that case.
if (conflictconfirms >= 0) {
return;
}
// Do not flush the wallet here for performance reasons.
CWalletDB walletdb(*dbw, "r+", false);
std::set todo;
std::set done;
todo.insert(hashTx);
while (!todo.empty()) {
uint256 now = *todo.begin();
todo.erase(now);
done.insert(now);
assert(mapWallet.count(now));
CWalletTx &wtx = mapWallet[now];
int currentconfirm = wtx.GetDepthInMainChain();
if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block.
wtx.nIndex = -1;
wtx.hashBlock = hashBlock;
wtx.MarkDirty();
walletdb.WriteTx(wtx);
// Iterate over all its outputs, and mark transactions in the wallet
// that spend them conflicted too.
TxSpends::const_iterator iter =
mapTxSpends.lower_bound(COutPoint(now, 0));
while (iter != mapTxSpends.end() && iter->first.hash == now) {
if (!done.count(iter->second)) {
todo.insert(iter->second);
}
iter++;
}
// If a transaction changes 'conflicted' state, that changes the
// balance available of the outputs it spends. So force those to be
// recomputed.
for (const CTxIn &txin : wtx.tx->vin) {
if (mapWallet.count(txin.prevout.hash)) {
mapWallet[txin.prevout.hash].MarkDirty();
}
}
}
}
}
void CWallet::SyncTransaction(const CTransactionRef &ptx,
const CBlockIndex *pindex, int posInBlock) {
const CTransaction &tx = *ptx;
if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, true)) {
// Not one of ours
return;
}
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be recomputed,
// also:
for (const CTxIn &txin : tx.vin) {
if (mapWallet.count(txin.prevout.hash)) {
mapWallet[txin.prevout.hash].MarkDirty();
}
}
}
void CWallet::TransactionAddedToMempool(const CTransactionRef &ptx) {
LOCK2(cs_main, cs_wallet);
SyncTransaction(ptx);
}
void CWallet::BlockConnected(
const std::shared_ptr &pblock, const CBlockIndex *pindex,
const std::vector &vtxConflicted) {
LOCK2(cs_main, cs_wallet);
// TODO: Tempoarily ensure that mempool removals are notified before
// connected transactions. This shouldn't matter, but the abandoned state of
// transactions in our wallet is currently cleared when we receive another
// notification and there is a race condition where notification of a
// connected conflict might cause an outside process to abandon a
// transaction and then have it inadvertantly cleared by the notification
// that the conflicted transaction was evicted.
for (const CTransactionRef &ptx : vtxConflicted) {
SyncTransaction(ptx);
}
for (size_t i = 0; i < pblock->vtx.size(); i++) {
SyncTransaction(pblock->vtx[i], pindex, i);
}
}
void CWallet::BlockDisconnected(const std::shared_ptr &pblock) {
LOCK2(cs_main, cs_wallet);
for (const CTransactionRef &ptx : pblock->vtx) {
SyncTransaction(ptx);
}
}
isminetype CWallet::IsMine(const CTxIn &txin) const {
LOCK(cs_wallet);
std::map::const_iterator mi =
mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end()) {
const CWalletTx &prev = (*mi).second;
if (txin.prevout.n < prev.tx->vout.size()) {
return IsMine(prev.tx->vout[txin.prevout.n]);
}
}
return ISMINE_NO;
}
// Note that this function doesn't distinguish between a 0-valued input, and a
// not-"is mine" (according to the filter) input.
Amount CWallet::GetDebit(const CTxIn &txin, const isminefilter &filter) const {
LOCK(cs_wallet);
std::map::const_iterator mi =
mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end()) {
const CWalletTx &prev = (*mi).second;
if (txin.prevout.n < prev.tx->vout.size()) {
if (IsMine(prev.tx->vout[txin.prevout.n]) & filter) {
return prev.tx->vout[txin.prevout.n].nValue;
}
}
}
return Amount(0);
}
isminetype CWallet::IsMine(const CTxOut &txout) const {
return ::IsMine(*this, txout.scriptPubKey);
}
Amount CWallet::GetCredit(const CTxOut &txout,
const isminefilter &filter) const {
if (!MoneyRange(txout.nValue)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
return (IsMine(txout) & filter) ? txout.nValue : Amount(0);
}
bool CWallet::IsChange(const CTxOut &txout) const {
// TODO: fix handling of 'change' outputs. The assumption is that any
// payment to a script that is ours, but is not in the address book is
// change. That assumption is likely to break when we implement
// multisignature wallets that return change back into a
// multi-signature-protected address; a better way of identifying which
// outputs are 'the send' and which are 'the change' will need to be
// implemented (maybe extend CWalletTx to remember which output, if any, was
// change).
if (::IsMine(*this, txout.scriptPubKey)) {
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address)) {
return true;
}
LOCK(cs_wallet);
if (!mapAddressBook.count(address)) {
return true;
}
}
return false;
}
Amount CWallet::GetChange(const CTxOut &txout) const {
if (!MoneyRange(txout.nValue)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
return (IsChange(txout) ? txout.nValue : Amount(0));
}
bool CWallet::IsMine(const CTransaction &tx) const {
for (const CTxOut &txout : tx.vout) {
if (IsMine(txout)) {
return true;
}
}
return false;
}
bool CWallet::IsFromMe(const CTransaction &tx) const {
return GetDebit(tx, ISMINE_ALL) > Amount(0);
}
Amount CWallet::GetDebit(const CTransaction &tx,
const isminefilter &filter) const {
Amount nDebit(0);
for (const CTxIn &txin : tx.vin) {
nDebit += GetDebit(txin, filter);
if (!MoneyRange(nDebit)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
}
return nDebit;
}
bool CWallet::IsAllFromMe(const CTransaction &tx,
const isminefilter &filter) const {
LOCK(cs_wallet);
for (const CTxIn &txin : tx.vin) {
auto mi = mapWallet.find(txin.prevout.hash);
if (mi == mapWallet.end()) {
// Any unknown inputs can't be from us.
return false;
}
const CWalletTx &prev = (*mi).second;
if (txin.prevout.n >= prev.tx->vout.size()) {
// Invalid input!
return false;
}
if (!(IsMine(prev.tx->vout[txin.prevout.n]) & filter)) {
return false;
}
}
return true;
}
Amount CWallet::GetCredit(const CTransaction &tx,
const isminefilter &filter) const {
Amount nCredit(0);
for (const CTxOut &txout : tx.vout) {
nCredit += GetCredit(txout, filter);
if (!MoneyRange(nCredit)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
}
return nCredit;
}
Amount CWallet::GetChange(const CTransaction &tx) const {
Amount nChange(0);
for (const CTxOut &txout : tx.vout) {
nChange += GetChange(txout);
if (!MoneyRange(nChange)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
}
return nChange;
}
CPubKey CWallet::GenerateNewHDMasterKey() {
CKey key;
key.MakeNewKey(true);
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
// Calculate the pubkey.
CPubKey pubkey = key.GetPubKey();
assert(key.VerifyPubKey(pubkey));
// Set the hd keypath to "m" -> Master, refers the masterkeyid to itself.
metadata.hdKeypath = "m";
metadata.hdMasterKeyID = pubkey.GetID();
LOCK(cs_wallet);
// mem store the metadata
mapKeyMetadata[pubkey.GetID()] = metadata;
// Write the key&metadata to the database.
if (!AddKeyPubKey(key, pubkey)) {
throw std::runtime_error(std::string(__func__) +
": AddKeyPubKey failed");
}
return pubkey;
}
bool CWallet::SetHDMasterKey(const CPubKey &pubkey,
CHDChain *possibleOldChain) {
LOCK(cs_wallet);
// Store the keyid (hash160) together with the child index counter in the
// database as a hdchain object.
CHDChain newHdChain;
if (possibleOldChain) {
// preserve the old chains version
newHdChain.nVersion = possibleOldChain->nVersion;
}
newHdChain.masterKeyID = pubkey.GetID();
SetHDChain(newHdChain, false);
return true;
}
bool CWallet::SetHDChain(const CHDChain &chain, bool memonly) {
LOCK(cs_wallet);
if (!memonly && !CWalletDB(*dbw).WriteHDChain(chain)) {
throw std::runtime_error(std::string(__func__) +
": writing chain failed");
}
hdChain = chain;
return true;
}
bool CWallet::IsHDEnabled() {
return !hdChain.masterKeyID.IsNull();
}
int64_t CWalletTx::GetTxTime() const {
int64_t n = nTimeSmart;
return n ? n : nTimeReceived;
}
int CWalletTx::GetRequestCount() const {
LOCK(pwallet->cs_wallet);
// Returns -1 if it wasn't being tracked.
int nRequests = -1;
if (IsCoinBase()) {
// Generated block.
if (!hashUnset()) {
std::map::const_iterator mi =
pwallet->mapRequestCount.find(hashBlock);
if (mi != pwallet->mapRequestCount.end()) {
nRequests = (*mi).second;
}
}
} else {
// Did anyone request this transaction?
std::map::const_iterator mi =
pwallet->mapRequestCount.find(GetId());
if (mi != pwallet->mapRequestCount.end()) {
nRequests = (*mi).second;
// How about the block it's in?
if (nRequests == 0 && !hashUnset()) {
std::map::const_iterator _mi =
pwallet->mapRequestCount.find(hashBlock);
if (_mi != pwallet->mapRequestCount.end()) {
nRequests = (*_mi).second;
} else {
// If it's in someone else's block it must have got out.
nRequests = 1;
}
}
}
}
return nRequests;
}
void CWalletTx::GetAmounts(std::list &listReceived,
std::list &listSent, Amount &nFee,
std::string &strSentAccount,
const isminefilter &filter) const {
nFee = Amount(0);
listReceived.clear();
listSent.clear();
strSentAccount = strFromAccount;
// Compute fee:
Amount nDebit = GetDebit(filter);
// debit>0 means we signed/sent this transaction.
if (nDebit > Amount(0)) {
Amount nValueOut = tx->GetValueOut();
nFee = (nDebit - nValueOut);
}
// Sent/received.
for (unsigned int i = 0; i < tx->vout.size(); ++i) {
const CTxOut &txout = tx->vout[i];
isminetype fIsMine = pwallet->IsMine(txout);
// Only need to handle txouts if AT LEAST one of these is true:
// 1) they debit from us (sent)
// 2) the output is to us (received)
if (nDebit > Amount(0)) {
// Don't report 'change' txouts
if (pwallet->IsChange(txout)) {
continue;
}
} else if (!(fIsMine & filter)) {
continue;
}
// In either case, we need to get the destination address.
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address) &&
!txout.scriptPubKey.IsUnspendable()) {
LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, "
"txid %s\n",
this->GetId().ToString());
address = CNoDestination();
}
COutputEntry output = {address, txout.nValue, (int)i};
// If we are debited by the transaction, add the output as a "sent"
// entry.
if (nDebit > Amount(0)) {
listSent.push_back(output);
}
// If we are receiving the output, add it as a "received" entry.
if (fIsMine & filter) {
listReceived.push_back(output);
}
}
}
/**
* Scan the block chain (starting in pindexStart) for transactions from or to
* us. If fUpdate is true, found transactions that already exist in the wallet
* will be updated.
*
* Returns pointer to the first block in the last contiguous range that was
* successfully scanned or elided (elided if pIndexStart points at a block
* before CWallet::nTimeFirstKey). Returns null if there is no such range, or
* the range doesn't include chainActive.Tip().
*/
CBlockIndex *CWallet::ScanForWalletTransactions(CBlockIndex *pindexStart,
bool fUpdate) {
LOCK2(cs_main, cs_wallet);
int64_t nNow = GetTime();
CBlockIndex *pindex = pindexStart;
CBlockIndex *ret = pindexStart;
// No need to read and scan block, if block was created before our wallet
// birthday (as adjusted for block time variability)
while (pindex && nTimeFirstKey &&
(pindex->GetBlockTime() < (nTimeFirstKey - 7200))) {
pindex = chainActive.Next(pindex);
}
// Show rescan progress in GUI as dialog or on splashscreen, if -rescan on
// startup.
ShowProgress(_("Rescanning..."), 0);
double dProgressStart =
GuessVerificationProgress(chainParams.TxData(), pindex);
double dProgressTip =
GuessVerificationProgress(chainParams.TxData(), chainActive.Tip());
while (pindex) {
if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) {
ShowProgress(
_("Rescanning..."),
std::max(1,
std::min(99, (int)((GuessVerificationProgress(
chainParams.TxData(), pindex) -
dProgressStart) /
(dProgressTip - dProgressStart) *
100))));
}
CBlock block;
if (ReadBlockFromDisk(block, pindex, GetConfig())) {
for (size_t posInBlock = 0; posInBlock < block.vtx.size();
++posInBlock) {
AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex,
posInBlock, fUpdate);
}
if (!ret) {
ret = pindex;
}
} else {
ret = nullptr;
}
pindex = chainActive.Next(pindex);
if (GetTime() >= nNow + 60) {
nNow = GetTime();
LogPrintf("Still rescanning. At block %d. Progress=%f\n",
pindex->nHeight,
GuessVerificationProgress(chainParams.TxData(), pindex));
}
}
// Hide progress dialog in GUI.
ShowProgress(_("Rescanning..."), 100);
return ret;
}
void CWallet::ReacceptWalletTransactions() {
// If transactions aren't being broadcasted, don't let them into local
// mempool either.
if (!fBroadcastTransactions) {
return;
}
LOCK2(cs_main, cs_wallet);
std::map mapSorted;
// Sort pending wallet transactions based on their initial wallet insertion
// order.
for (std::pair &item : mapWallet) {
const uint256 &wtxid = item.first;
CWalletTx &wtx = item.second;
assert(wtx.GetId() == wtxid);
int nDepth = wtx.GetDepthInMainChain();
if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
}
}
// Try to add wallet transactions to memory pool.
for (std::pair &item : mapSorted) {
CWalletTx &wtx = *(item.second);
LOCK(mempool.cs);
CValidationState state;
wtx.AcceptToMemoryPool(maxTxFee, state);
}
}
bool CWalletTx::RelayWalletTransaction(CConnman *connman) {
assert(pwallet->GetBroadcastTransactions());
if (IsCoinBase() || isAbandoned() || GetDepthInMainChain() != 0) {
return false;
}
CValidationState state;
// GetDepthInMainChain already catches known conflicts.
if (InMempool() || AcceptToMemoryPool(maxTxFee, state)) {
LogPrintf("Relaying wtx %s\n", GetId().ToString());
if (connman) {
CInv inv(MSG_TX, GetId());
connman->ForEachNode(
[&inv](CNode *pnode) { pnode->PushInventory(inv); });
return true;
}
}
return false;
}
std::set CWalletTx::GetConflicts() const {
std::set result;
if (pwallet != nullptr) {
uint256 myHash = GetId();
result = pwallet->GetConflicts(myHash);
result.erase(myHash);
}
return result;
}
Amount CWalletTx::GetDebit(const isminefilter &filter) const {
if (tx->vin.empty()) return Amount(0);
Amount debit(0);
if (filter & ISMINE_SPENDABLE) {
if (fDebitCached) {
debit += nDebitCached;
} else {
nDebitCached = pwallet->GetDebit(*this, ISMINE_SPENDABLE);
fDebitCached = true;
debit += nDebitCached;
}
}
if (filter & ISMINE_WATCH_ONLY) {
if (fWatchDebitCached) {
debit += nWatchDebitCached;
} else {
nWatchDebitCached = pwallet->GetDebit(*this, ISMINE_WATCH_ONLY);
fWatchDebitCached = true;
debit += Amount(nWatchDebitCached);
}
}
return debit;
}
Amount CWalletTx::GetCredit(const isminefilter &filter) const {
// Must wait until coinbase is safely deep enough in the chain before
// valuing it.
if (IsCoinBase() && GetBlocksToMaturity() > 0) {
return Amount(0);
}
Amount credit(0);
if (filter & ISMINE_SPENDABLE) {
// GetBalance can assume transactions in mapWallet won't change.
if (fCreditCached) {
credit += nCreditCached;
} else {
nCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
fCreditCached = true;
credit += nCreditCached;
}
}
if (filter & ISMINE_WATCH_ONLY) {
if (fWatchCreditCached) {
credit += nWatchCreditCached;
} else {
nWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
fWatchCreditCached = true;
credit += nWatchCreditCached;
}
}
return credit;
}
Amount CWalletTx::GetImmatureCredit(bool fUseCache) const {
if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain()) {
if (fUseCache && fImmatureCreditCached) return nImmatureCreditCached;
nImmatureCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
fImmatureCreditCached = true;
return nImmatureCreditCached;
}
return Amount(0);
}
Amount CWalletTx::GetAvailableCredit(bool fUseCache) const {
if (pwallet == 0) {
return Amount(0);
}
// Must wait until coinbase is safely deep enough in the chain before
// valuing it.
if (IsCoinBase() && GetBlocksToMaturity() > 0) {
return Amount(0);
}
if (fUseCache && fAvailableCreditCached) {
return nAvailableCreditCached;
}
Amount nCredit(0);
uint256 hashTx = GetId();
for (unsigned int i = 0; i < tx->vout.size(); i++) {
if (!pwallet->IsSpent(hashTx, i)) {
const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE);
if (!MoneyRange(nCredit)) {
throw std::runtime_error(
"CWalletTx::GetAvailableCredit() : value out of range");
}
}
}
nAvailableCreditCached = nCredit;
fAvailableCreditCached = true;
return nCredit;
}
Amount CWalletTx::GetImmatureWatchOnlyCredit(const bool &fUseCache) const {
if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain()) {
if (fUseCache && fImmatureWatchCreditCached) {
return nImmatureWatchCreditCached;
}
nImmatureWatchCreditCached =
pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
fImmatureWatchCreditCached = true;
return nImmatureWatchCreditCached;
}
return Amount(0);
}
Amount CWalletTx::GetAvailableWatchOnlyCredit(const bool &fUseCache) const {
if (pwallet == 0) {
return Amount(0);
}
// Must wait until coinbase is safely deep enough in the chain before
// valuing it.
if (IsCoinBase() && GetBlocksToMaturity() > 0) {
return Amount(0);
}
if (fUseCache && fAvailableWatchCreditCached) {
return nAvailableWatchCreditCached;
}
Amount nCredit(0);
for (unsigned int i = 0; i < tx->vout.size(); i++) {
if (!pwallet->IsSpent(GetId(), i)) {
const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY);
if (!MoneyRange(nCredit)) {
throw std::runtime_error(
"CWalletTx::GetAvailableCredit() : value out of range");
}
}
}
nAvailableWatchCreditCached = nCredit;
fAvailableWatchCreditCached = true;
return nCredit;
}
Amount CWalletTx::GetChange() const {
if (fChangeCached) {
return nChangeCached;
}
nChangeCached = pwallet->GetChange(*this);
fChangeCached = true;
return nChangeCached;
}
bool CWalletTx::InMempool() const {
LOCK(mempool.cs);
if (mempool.exists(GetId())) {
return true;
}
return false;
}
bool CWalletTx::IsTrusted() const {
// Quick answer in most cases
if (!CheckFinalTx(*this)) {
return false;
}
int nDepth = GetDepthInMainChain();
if (nDepth >= 1) {
return true;
}
if (nDepth < 0) {
return false;
}
// using wtx's cached debit
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) {
return false;
}
// Don't trust unconfirmed transactions from us unless they are in the
// mempool.
if (!InMempool()) {
return false;
}
// Trusted if all inputs are from us and are in the mempool:
for (const CTxIn &txin : tx->vin) {
// Transactions not sent by us: not trusted
const CWalletTx *parent = pwallet->GetWalletTx(txin.prevout.hash);
if (parent == nullptr) {
return false;
}
const CTxOut &parentOut = parent->tx->vout[txin.prevout.n];
if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) {
return false;
}
}
return true;
}
bool CWalletTx::IsEquivalentTo(const CWalletTx &_tx) const {
CMutableTransaction tx1 = *this->tx;
CMutableTransaction tx2 = *_tx.tx;
for (unsigned int i = 0; i < tx1.vin.size(); i++) {
tx1.vin[i].scriptSig = CScript();
}
for (unsigned int i = 0; i < tx2.vin.size(); i++) {
tx2.vin[i].scriptSig = CScript();
}
return CTransaction(tx1) == CTransaction(tx2);
}
std::vector
CWallet::ResendWalletTransactionsBefore(int64_t nTime, CConnman *connman) {
std::vector result;
LOCK(cs_wallet);
// Sort them in chronological order
std::multimap mapSorted;
for (std::pair &item : mapWallet) {
CWalletTx &wtx = item.second;
// Don't rebroadcast if newer than nTime:
if (wtx.nTimeReceived > nTime) {
continue;
}
mapSorted.insert(std::make_pair(wtx.nTimeReceived, &wtx));
}
for (std::pair &item : mapSorted) {
CWalletTx &wtx = *item.second;
if (wtx.RelayWalletTransaction(connman)) {
result.push_back(wtx.GetId());
}
}
return result;
}
void CWallet::ResendWalletTransactions(int64_t nBestBlockTime,
CConnman *connman) {
// Do this infrequently and randomly to avoid giving away that these are our
// transactions.
if (GetTime() < nNextResend || !fBroadcastTransactions) {
return;
}
bool fFirst = (nNextResend == 0);
nNextResend = GetTime() + GetRand(30 * 60);
if (fFirst) {
return;
}
// Only do it if there's been a new block since last time
if (nBestBlockTime < nLastResend) {
return;
}
nLastResend = GetTime();
// Rebroadcast unconfirmed txes older than 5 minutes before the last block
// was found:
std::vector relayed =
ResendWalletTransactionsBefore(nBestBlockTime - 5 * 60, connman);
if (!relayed.empty()) {
LogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__,
relayed.size());
}
}
/** @} */ // end of mapWallet
/**
* @defgroup Actions
*
* @{
*/
Amount CWallet::GetBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal(0);
for (std::map::const_iterator it = mapWallet.begin();
it != mapWallet.end(); ++it) {
const CWalletTx *pcoin = &(*it).second;
if (pcoin->IsTrusted()) {
nTotal += pcoin->GetAvailableCredit();
}
}
return nTotal;
}
Amount CWallet::GetUnconfirmedBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal(0);
for (std::map::const_iterator it = mapWallet.begin();
it != mapWallet.end(); ++it) {
const CWalletTx *pcoin = &(*it).second;
if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 &&
pcoin->InMempool()) {
nTotal += pcoin->GetAvailableCredit();
}
}
return nTotal;
}
Amount CWallet::GetImmatureBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal(0);
for (std::map::const_iterator it = mapWallet.begin();
it != mapWallet.end(); ++it) {
const CWalletTx *pcoin = &(*it).second;
nTotal += pcoin->GetImmatureCredit();
}
return nTotal;
}
Amount CWallet::GetWatchOnlyBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal(0);
for (std::map::const_iterator it = mapWallet.begin();
it != mapWallet.end(); ++it) {
const CWalletTx *pcoin = &(*it).second;
if (pcoin->IsTrusted()) {
nTotal += pcoin->GetAvailableWatchOnlyCredit();
}
}
return nTotal;
}
Amount CWallet::GetUnconfirmedWatchOnlyBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal(0);
for (std::map::const_iterator it = mapWallet.begin();
it != mapWallet.end(); ++it) {
const CWalletTx *pcoin = &(*it).second;
if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 &&
pcoin->InMempool()) {
nTotal += pcoin->GetAvailableWatchOnlyCredit();
}
}
return nTotal;
}
Amount CWallet::GetImmatureWatchOnlyBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal(0);
for (std::map::const_iterator it = mapWallet.begin();
it != mapWallet.end(); ++it) {
const CWalletTx *pcoin = &(*it).second;
nTotal += pcoin->GetImmatureWatchOnlyCredit();
}
return nTotal;
}
// Calculate total balance in a different way from GetBalance. The biggest
// difference is that GetBalance sums up all unspent TxOuts paying to the
// wallet, while this sums up both spent and unspent TxOuts paying to the
// wallet, and then subtracts the values of TxIns spending from the wallet. This
// also has fewer restrictions on which unconfirmed transactions are considered
// trusted.
Amount CWallet::GetLegacyBalance(const isminefilter &filter, int minDepth,
const std::string *account) const {
LOCK2(cs_main, cs_wallet);
Amount balance(0);
for (const auto &entry : mapWallet) {
const CWalletTx &wtx = entry.second;
const int depth = wtx.GetDepthInMainChain();
if (depth < 0 || !CheckFinalTx(*wtx.tx) ||
wtx.GetBlocksToMaturity() > 0) {
continue;
}
// Loop through tx outputs and add incoming payments. For outgoing txs,
// treat change outputs specially, as part of the amount debited.
Amount debit = wtx.GetDebit(filter);
const bool outgoing = debit > Amount(0);
for (const CTxOut &out : wtx.tx->vout) {
if (outgoing && IsChange(out)) {
debit -= out.nValue;
} else if (IsMine(out) & filter && depth >= minDepth &&
(!account ||
*account == GetAccountName(out.scriptPubKey))) {
balance += out.nValue;
}
}
// For outgoing txs, subtract amount debited.
if (outgoing && (!account || *account == wtx.strFromAccount)) {
balance -= debit;
}
}
if (account) {
balance += CWalletDB(*dbw).GetAccountCreditDebit(*account);
}
return balance;
}
void CWallet::AvailableCoins(std::vector &vCoins, bool fOnlySafe,
const CCoinControl *coinControl,
bool fIncludeZeroValue) const {
vCoins.clear();
LOCK2(cs_main, cs_wallet);
for (std::map::const_iterator it = mapWallet.begin();
it != mapWallet.end(); ++it) {
const uint256 &wtxid = it->first;
const CWalletTx *pcoin = &(*it).second;
if (!CheckFinalTx(*pcoin)) {
continue;
}
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) {
continue;
}
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < 0) {
continue;
}
// We should not consider coins which aren't at least in our mempool.
// It's possible for these to be conflicted via ancestors which we may
// never be able to detect.
if (nDepth == 0 && !pcoin->InMempool()) {
continue;
}
bool safeTx = pcoin->IsTrusted();
// Bitcoin-ABC: Removed check that prevents consideration of coins from
// transactions that are replacing other transactions. This check based
// on pcoin->mapValue.count("replaces_txid") which was not being set
// anywhere.
// Similarly, we should not consider coins from transactions that have
// been replaced. In the example above, we would want to prevent
// creation of a transaction A' spending an output of A, because if
// transaction B were initially confirmed, conflicting with A and A', we
// wouldn't want to the user to create a transaction D intending to
// replace A', but potentially resulting in a scenario where A, A', and
// D could all be accepted (instead of just B and D, or just A and A'
// like the user would want).
// Bitcoin-ABC: retained this check as 'replaced_by_txid' is still set
// in the wallet code.
if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) {
safeTx = false;
}
if (fOnlySafe && !safeTx) {
continue;
}
for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
isminetype mine = IsMine(pcoin->tx->vout[i]);
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
!IsLockedCoin((*it).first, i) &&
(pcoin->tx->vout[i].nValue > Amount(0) || fIncludeZeroValue) &&
(!coinControl || !coinControl->HasSelected() ||
coinControl->fAllowOtherInputs ||
coinControl->IsSelected(COutPoint((*it).first, i)))) {
vCoins.push_back(COutput(
pcoin, i, nDepth,
((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
(coinControl && coinControl->fAllowWatchOnly &&
(mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO),
(mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) !=
ISMINE_NO,
safeTx));
}
}
}
}
static void ApproximateBestSubset(
std::vector>>
vValue,
const Amount nTotalLower, const Amount nTargetValue,
std::vector &vfBest, Amount &nBest, int iterations = 1000) {
std::vector vfIncluded;
vfBest.assign(vValue.size(), true);
nBest = nTotalLower;
FastRandomContext insecure_rand;
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) {
vfIncluded.assign(vValue.size(), false);
Amount nTotal(0);
bool fReachedTarget = false;
for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) {
for (size_t i = 0; i < vValue.size(); i++) {
// The solver here uses a randomized algorithm, the randomness
// serves no real security purpose but is just needed to prevent
// degenerate behavior and it is important that the rng is fast.
// We do not use a constant random sequence, because there may
// be some privacy improvement by making the selection random.
if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i]) {
nTotal += vValue[i].first;
vfIncluded[i] = true;
if (nTotal >= nTargetValue) {
fReachedTarget = true;
if (nTotal < nBest) {
nBest = nTotal;
vfBest = vfIncluded;
}
nTotal -= vValue[i].first;
vfIncluded[i] = false;
}
}
}
}
}
}
bool CWallet::SelectCoinsMinConf(
const Amount nTargetValue, const int nConfMine, const int nConfTheirs,
const uint64_t nMaxAncestors, std::vector vCoins,
std::set> &setCoinsRet,
Amount &nValueRet) const {
setCoinsRet.clear();
nValueRet = Amount(0);
// List of values less than target
std::pair>
coinLowestLarger;
coinLowestLarger.first = MAX_MONEY;
coinLowestLarger.second.first = nullptr;
std::vector>>
vValue;
Amount nTotalLower(0);
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
for (const COutput &output : vCoins) {
if (!output.fSpendable) {
continue;
}
const CWalletTx *pcoin = output.tx;
if (output.nDepth <
(pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) {
continue;
}
if (!mempool.TransactionWithinChainLimit(pcoin->GetId(),
nMaxAncestors)) {
continue;
}
int i = output.i;
Amount n = pcoin->tx->vout[i].nValue;
std::pair> coin =
std::make_pair(n, std::make_pair(pcoin, i));
if (n == nTargetValue) {
setCoinsRet.insert(coin.second);
nValueRet += coin.first;
return true;
} else if (n < nTargetValue + MIN_CHANGE) {
vValue.push_back(coin);
nTotalLower += n;
} else if (n < coinLowestLarger.first) {
coinLowestLarger = coin;
}
}
if (nTotalLower == nTargetValue) {
for (unsigned int i = 0; i < vValue.size(); ++i) {
setCoinsRet.insert(vValue[i].second);
nValueRet += vValue[i].first;
}
return true;
}
if (nTotalLower < nTargetValue) {
if (coinLowestLarger.second.first == nullptr) {
return false;
}
setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first;
return true;
}
// Solve subset sum by stochastic approximation
std::sort(vValue.begin(), vValue.end(), CompareValueOnly());
std::reverse(vValue.begin(), vValue.end());
std::vector vfBest;
Amount nBest;
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest);
if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) {
ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE,
vfBest, nBest);
}
// If we have a bigger coin and (either the stochastic approximation didn't
// find a good solution, or the next bigger coin is closer), return the
// bigger coin.
if (coinLowestLarger.second.first &&
((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) ||
coinLowestLarger.first <= nBest)) {
setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first;
} else {
for (unsigned int i = 0; i < vValue.size(); i++) {
if (vfBest[i]) {
setCoinsRet.insert(vValue[i].second);
nValueRet += vValue[i].first;
}
}
if (LogAcceptCategory(BCLog::SELECTCOINS)) {
LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: ");
for (size_t i = 0; i < vValue.size(); i++) {
if (vfBest[i]) {
LogPrint(BCLog::SELECTCOINS, "%s ",
FormatMoney(vValue[i].first));
}
}
LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
}
}
return true;
}
bool CWallet::SelectCoins(
const std::vector &vAvailableCoins, const Amount nTargetValue,
std::set> &setCoinsRet,
Amount &nValueRet, const CCoinControl *coinControl) const {
std::vector vCoins(vAvailableCoins);
// coin control -> return all selected outputs (we want all selected to go
// into the transaction for sure).
if (coinControl && coinControl->HasSelected() &&
!coinControl->fAllowOtherInputs) {
for (const COutput &out : vCoins) {
if (!out.fSpendable) {
continue;
}
nValueRet += out.tx->tx->vout[out.i].nValue;
setCoinsRet.insert(std::make_pair(out.tx, out.i));
}
return (nValueRet >= nTargetValue);
}
// Calculate value from preset inputs and store them.
std::set> setPresetCoins;
Amount nValueFromPresetInputs(0);
std::vector vPresetInputs;
if (coinControl) {
coinControl->ListSelected(vPresetInputs);
}
for (const COutPoint &outpoint : vPresetInputs) {
std::map::const_iterator it =
mapWallet.find(outpoint.hash);
if (it == mapWallet.end()) {
// TODO: Allow non-wallet inputs
return false;
}
const CWalletTx *pcoin = &it->second;
// Clearly invalid input, fail.
if (pcoin->tx->vout.size() <= outpoint.n) {
return false;
}
nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue;
setPresetCoins.insert(std::make_pair(pcoin, outpoint.n));
}
// Remove preset inputs from vCoins.
for (std::vector::iterator it = vCoins.begin();
it != vCoins.end() && coinControl && coinControl->HasSelected();) {
if (setPresetCoins.count(std::make_pair(it->tx, it->i))) {
it = vCoins.erase(it);
} else {
++it;
}
}
size_t nMaxChainLength = std::min(
gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT),
gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT));
bool fRejectLongChains = gArgs.GetBoolArg(
"-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
bool res =
nTargetValue <= nValueFromPresetInputs ||
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 6, 0,
vCoins, setCoinsRet, nValueRet) ||
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, 0,
vCoins, setCoinsRet, nValueRet) ||
(bSpendZeroConfChange &&
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, 2,
vCoins, setCoinsRet, nValueRet)) ||
(bSpendZeroConfChange &&
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1,
std::min((size_t)4, nMaxChainLength / 3), vCoins,
setCoinsRet, nValueRet)) ||
(bSpendZeroConfChange &&
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1,
nMaxChainLength / 2, vCoins, setCoinsRet,
nValueRet)) ||
(bSpendZeroConfChange &&
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1,
nMaxChainLength, vCoins, setCoinsRet, nValueRet)) ||
(bSpendZeroConfChange && !fRejectLongChains &&
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1,
std::numeric_limits::max(), vCoins,
setCoinsRet, nValueRet));
// Because SelectCoinsMinConf clears the setCoinsRet, we now add the
// possible inputs to the coinset.
setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end());
// Add preset inputs to the total value selected.
nValueRet += nValueFromPresetInputs;
return res;
}
bool CWallet::FundTransaction(CMutableTransaction &tx, Amount &nFeeRet,
bool overrideEstimatedFeeRate,
const CFeeRate &specificFeeRate,
int &nChangePosInOut, std::string &strFailReason,
bool includeWatching, bool lockUnspents,
const std::set &setSubtractFeeFromOutputs,
bool keepReserveKey,
const CTxDestination &destChange) {
std::vector vecSend;
// Turn the txout set into a CRecipient vector.
for (size_t idx = 0; idx < tx.vout.size(); idx++) {
const CTxOut &txOut = tx.vout[idx];
CRecipient recipient = {txOut.scriptPubKey, txOut.nValue,
setSubtractFeeFromOutputs.count(idx) == 1};
vecSend.push_back(recipient);
}
CCoinControl coinControl;
coinControl.destChange = destChange;
coinControl.fAllowOtherInputs = true;
coinControl.fAllowWatchOnly = includeWatching;
coinControl.fOverrideFeeRate = overrideEstimatedFeeRate;
coinControl.nFeeRate = specificFeeRate;
for (const CTxIn &txin : tx.vin) {
coinControl.Select(txin.prevout);
}
CReserveKey reservekey(this);
CWalletTx wtx;
if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut,
strFailReason, &coinControl, false)) {
return false;
}
if (nChangePosInOut != -1) {
tx.vout.insert(tx.vout.begin() + nChangePosInOut,
wtx.tx->vout[nChangePosInOut]);
}
// Copy output sizes from new transaction; they may have had the fee
// subtracted from them.
for (size_t idx = 0; idx < tx.vout.size(); idx++) {
tx.vout[idx].nValue = wtx.tx->vout[idx].nValue;
}
// Add new txins (keeping original txin scriptSig/order)
for (const CTxIn &txin : wtx.tx->vin) {
if (!coinControl.IsSelected(txin.prevout)) {
tx.vin.push_back(txin);
if (lockUnspents) {
LOCK2(cs_main, cs_wallet);
LockCoin(txin.prevout);
}
}
}
// Optionally keep the change output key.
if (keepReserveKey) {
reservekey.KeepKey();
}
return true;
}
bool CWallet::CreateTransaction(const std::vector &vecSend,
CWalletTx &wtxNew, CReserveKey &reservekey,
Amount &nFeeRet, int &nChangePosInOut,
std::string &strFailReason,
const CCoinControl *coinControl, bool sign) {
Amount nValue(0);
int nChangePosRequest = nChangePosInOut;
unsigned int nSubtractFeeFromAmount = 0;
for (const auto &recipient : vecSend) {
if (nValue < Amount(0) || recipient.nAmount < Amount(0)) {
strFailReason = _("Transaction amounts must not be negative");
return false;
}
nValue += recipient.nAmount;
if (recipient.fSubtractFeeFromAmount) {
nSubtractFeeFromAmount++;
}
}
if (vecSend.empty()) {
strFailReason = _("Transaction must have at least one recipient");
return false;
}
wtxNew.fTimeReceivedIsTxTime = true;
wtxNew.BindWallet(this);
CMutableTransaction txNew;
// Discourage fee sniping.
//
// For a large miner the value of the transactions in the best block and the
// mempool can exceed the cost of deliberately attempting to mine two blocks
// to orphan the current best block. By setting nLockTime such that only the
// next block can include the transaction, we discourage this practice as
// the height restricted and limited blocksize gives miners considering fee
// sniping fewer options for pulling off this attack.
//
// A simple way to think about this is from the wallet's point of view we
// always want the blockchain to move forward. By setting nLockTime this way
// we're basically making the statement that we only want this transaction
// to appear in the next block; we don't want to potentially encourage
// reorgs by allowing transactions to appear at lower heights than the next
// block in forks of the best chain.
//
// Of course, the subsidy is high enough, and transaction volume low enough,
// that fee sniping isn't a problem yet, but by implementing a fix now we
// ensure code won't be written that makes assumptions about nLockTime that
// preclude a fix later.
txNew.nLockTime = chainActive.Height();
// Secondly occasionally randomly pick a nLockTime even further back, so
// that transactions that are delayed after signing for whatever reason,
// e.g. high-latency mix networks and some CoinJoin implementations, have
// better privacy.
if (GetRandInt(10) == 0) {
txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100));
}
assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
{
std::set> setCoins;
LOCK2(cs_main, cs_wallet);
std::vector vAvailableCoins;
AvailableCoins(vAvailableCoins, true, coinControl);
nFeeRet = Amount(0);
// Start with no fee and loop until there is enough fee.
while (true) {
nChangePosInOut = nChangePosRequest;
txNew.vin.clear();
txNew.vout.clear();
wtxNew.fFromMe = true;
bool fFirst = true;
Amount nValueToSelect = nValue;
if (nSubtractFeeFromAmount == 0) {
nValueToSelect += nFeeRet;
}
double dPriority = 0;
// vouts to the payees
for (const auto &recipient : vecSend) {
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
if (recipient.fSubtractFeeFromAmount) {
// Subtract fee equally from each selected recipient.
txout.nValue -= nFeeRet / int(nSubtractFeeFromAmount);
// First receiver pays the remainder not divisible by output
// count.
if (fFirst) {
fFirst = false;
txout.nValue -= nFeeRet % int(nSubtractFeeFromAmount);
}
}
if (txout.IsDust(dustRelayFee)) {
if (recipient.fSubtractFeeFromAmount &&
nFeeRet > Amount(0)) {
if (txout.nValue < Amount(0)) {
strFailReason = _("The transaction amount is "
"too small to pay the fee");
} else {
strFailReason =
_("The transaction amount is too small to "
"send after the fee has been deducted");
}
} else {
strFailReason = _("Transaction amount too small");
}
return false;
}
txNew.vout.push_back(txout);
}
// Choose coins to use.
Amount nValueIn(0);
setCoins.clear();
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins,
nValueIn, coinControl)) {
strFailReason = _("Insufficient funds");
return false;
}
for (const auto &pcoin : setCoins) {
Amount nCredit = pcoin.first->tx->vout[pcoin.second].nValue;
// The coin age after the next block (depth+1) is used instead
// of the current, reflecting an assumption the user would
// accept a bit more delay for a chance at a free transaction.
// But mempool inputs might still be in the mempool, so their
// age stays 0.
int age = pcoin.first->GetDepthInMainChain();
assert(age >= 0);
if (age != 0) age += 1;
dPriority += (double)nCredit.GetSatoshis() * age;
}
const Amount nChange = nValueIn - nValueToSelect;
if (nChange > Amount(0)) {
// Fill a vout to ourself.
// TODO: pass in scriptChange instead of reservekey so change
// transaction isn't always pay-to-bitcoin-address.
CScript scriptChange;
// Coin control: send change to custom address.
if (coinControl &&
!boost::get(&coinControl->destChange)) {
scriptChange =
GetScriptForDestination(coinControl->destChange);
// No coin control: send change to newly generated address.
} else {
// Note: We use a new key here to keep it from being obvious
// which side is the change. The drawback is that by not
// reusing a previous key, the change may be lost if a
// backup is restored, if the backup doesn't have the new
// private key for the change. If we reused the old key, it
// would be possible to add code to look for and rediscover
// unknown transactions that were written with keys of ours
// to recover post-backup change.
// Reserve a new key pair from key pool.
CPubKey vchPubKey;
bool ret;
ret = reservekey.GetReservedKey(vchPubKey, true);
if (!ret) {
strFailReason = _("Keypool ran out, please call "
"keypoolrefill first");
return false;
}
scriptChange = GetScriptForDestination(vchPubKey.GetID());
}
CTxOut newTxOut(nChange, scriptChange);
// We do not move dust-change to fees, because the sender would
// end up paying more than requested. This would be against the
// purpose of the all-inclusive feature. So instead we raise the
// change and deduct from the recipient.
if (nSubtractFeeFromAmount > 0 &&
newTxOut.IsDust(dustRelayFee)) {
Amount nDust = newTxOut.GetDustThreshold(dustRelayFee) -
newTxOut.nValue;
// Raise change until no more dust.
newTxOut.nValue += nDust;
// Subtract from first recipient.
for (unsigned int i = 0; i < vecSend.size(); i++) {
if (vecSend[i].fSubtractFeeFromAmount) {
txNew.vout[i].nValue -= nDust;
if (txNew.vout[i].IsDust(dustRelayFee)) {
strFailReason =
_("The transaction amount is too small "
"to send after the fee has been "
"deducted");
return false;
}
break;
}
}
}
// Never create dust outputs; if we would, just add the dust to
// the fee.
if (newTxOut.IsDust(dustRelayFee)) {
nChangePosInOut = -1;
nFeeRet += nChange;
reservekey.ReturnKey();
} else {
if (nChangePosInOut == -1) {
// Insert change txn at random position:
nChangePosInOut = GetRandInt(txNew.vout.size() + 1);
} else if ((unsigned int)nChangePosInOut >
txNew.vout.size()) {
strFailReason = _("Change index out of range");
return false;
}
std::vector::iterator position =
txNew.vout.begin() + nChangePosInOut;
txNew.vout.insert(position, newTxOut);
}
} else {
reservekey.ReturnKey();
}
// Fill vin
//
// Note how the sequence number is set to non-maxint so that the
// nLockTime set above actually works.
for (const auto &coin : setCoins) {
txNew.vin.push_back(
CTxIn(coin.first->GetId(), coin.second, CScript(),
std::numeric_limits::max() - 1));
}
// Fill in dummy signatures for fee calculation.
if (!DummySignTx(txNew, setCoins)) {
strFailReason = _("Signing transaction failed");
return false;
}
CTransaction txNewConst(txNew);
unsigned int nBytes = txNewConst.GetTotalSize();
dPriority = txNewConst.ComputePriority(dPriority, nBytes);
// Remove scriptSigs to eliminate the fee calculation dummy
// signatures.
for (auto &vin : txNew.vin) {
vin.scriptSig = CScript();
}
// Allow to override the default confirmation target over the
// CoinControl instance.
int currentConfirmationTarget = nTxConfirmTarget;
if (coinControl && coinControl->nConfirmTarget > 0) {
currentConfirmationTarget = coinControl->nConfirmTarget;
}
Amount nFeeNeeded =
GetMinimumFee(nBytes, currentConfirmationTarget, mempool);
if (coinControl && nFeeNeeded > Amount(0) &&
coinControl->nMinimumTotalFee > nFeeNeeded) {
nFeeNeeded = coinControl->nMinimumTotalFee;
}
if (coinControl && coinControl->fOverrideFeeRate) {
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
}
// If we made it here and we aren't even able to meet the relay fee
// on the next pass, give up because we must be at the maximum
// allowed fee.
if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) {
strFailReason = _("Transaction too large for fee policy");
return false;
}
if (nFeeRet >= nFeeNeeded) {
// Reduce fee to only the needed amount if we have change output
// to increase. This prevents potential overpayment in fees if
// the coins selected to meet nFeeNeeded result in a transaction
// that requires less fee than the prior iteration.
// TODO: The case where nSubtractFeeFromAmount > 0 remains to be
// addressed because it requires returning the fee to the payees
// and not the change output.
// TODO: The case where there is no change output remains to be
// addressed so we avoid creating too small an output.
if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 &&
nSubtractFeeFromAmount == 0) {
Amount extraFeePaid = nFeeRet - nFeeNeeded;
std::vector::iterator change_position =
txNew.vout.begin() + nChangePosInOut;
change_position->nValue += extraFeePaid;
nFeeRet -= extraFeePaid;
}
// Done, enough fee included.
break;
}
// Try to reduce change to include necessary fee.
if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
Amount additionalFeeNeeded = nFeeNeeded - nFeeRet;
std::vector::iterator change_position =
txNew.vout.begin() + nChangePosInOut;
// Only reduce change if remaining amount is still a large
// enough output.
if (change_position->nValue >=
MIN_FINAL_CHANGE + additionalFeeNeeded) {
change_position->nValue -= additionalFeeNeeded;
nFeeRet += additionalFeeNeeded;
// Done, able to increase fee from change.
break;
}
}
// Include more fee and try again.
nFeeRet = nFeeNeeded;
continue;
}
if (sign) {
SigHashType sigHashType = SigHashType().withForkId();
CTransaction txNewConst(txNew);
int nIn = 0;
for (const auto &coin : setCoins) {
const CScript &scriptPubKey =
coin.first->tx->vout[coin.second].scriptPubKey;
SignatureData sigdata;
if (!ProduceSignature(
TransactionSignatureCreator(
this, &txNewConst, nIn,
coin.first->tx->vout[coin.second].nValue,
sigHashType),
scriptPubKey, sigdata)) {
strFailReason = _("Signing transaction failed");
return false;
} else {
UpdateTransaction(txNew, nIn, sigdata);
}
nIn++;
}
}
// Embed the constructed transaction data in wtxNew.
wtxNew.SetTx(MakeTransactionRef(std::move(txNew)));
// Limit size.
if (CTransaction(wtxNew).GetTotalSize() >= MAX_STANDARD_TX_SIZE) {
strFailReason = _("Transaction too large");
return false;
}
}
if (gArgs.GetBoolArg("-walletrejectlongchains",
DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits.
LockPoints lp;
CTxMemPoolEntry entry(wtxNew.tx, Amount(0), 0, 0, 0, Amount(0), false,
0, lp);
CTxMemPool::setEntries setAncestors;
size_t nLimitAncestors =
gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
size_t nLimitAncestorSize =
gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) *
1000;
size_t nLimitDescendants =
gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
size_t nLimitDescendantSize =
gArgs.GetArg("-limitdescendantsize",
DEFAULT_DESCENDANT_SIZE_LIMIT) *
1000;
std::string errString;
if (!mempool.CalculateMemPoolAncestors(
entry, setAncestors, nLimitAncestors, nLimitAncestorSize,
nLimitDescendants, nLimitDescendantSize, errString)) {
strFailReason = _("Transaction has too long of a mempool chain");
return false;
}
}
return true;
}
/**
* Call after CreateTransaction unless you want to abort
*/
bool CWallet::CommitTransaction(CWalletTx &wtxNew, CReserveKey &reservekey,
CConnman *connman, CValidationState &state) {
LOCK2(cs_main, cs_wallet);
LogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString());
// Take key pair from key pool so it won't be used again.
reservekey.KeepKey();
// Add tx to wallet, because if it has change it's also ours, otherwise just
// for transaction history.
AddToWallet(wtxNew);
// Notify that old coins are spent.
for (const CTxIn &txin : wtxNew.tx->vin) {
CWalletTx &coin = mapWallet[txin.prevout.hash];
coin.BindWallet(this);
NotifyTransactionChanged(this, coin.GetId(), CT_UPDATED);
}
// Track how many getdata requests our transaction gets.
mapRequestCount[wtxNew.GetId()] = 0;
if (fBroadcastTransactions) {
// Broadcast
if (!wtxNew.AcceptToMemoryPool(maxTxFee, state)) {
LogPrintf("CommitTransaction(): Transaction cannot be "
"broadcast immediately, %s\n",
state.GetRejectReason());
// TODO: if we expect the failure to be long term or permanent,
// instead delete wtx from the wallet and return failure.
} else {
wtxNew.RelayWalletTransaction(connman);
}
}
return true;
}
void CWallet::ListAccountCreditDebit(const std::string &strAccount,
std::list &entries) {
CWalletDB walletdb(*dbw);
return walletdb.ListAccountCreditDebit(strAccount, entries);
}
bool CWallet::AddAccountingEntry(const CAccountingEntry &acentry) {
CWalletDB walletdb(*dbw);
return AddAccountingEntry(acentry, &walletdb);
}
bool CWallet::AddAccountingEntry(const CAccountingEntry &acentry,
CWalletDB *pwalletdb) {
if (!pwalletdb->WriteAccountingEntry(++nAccountingEntryNumber, acentry)) {
return false;
}
laccentries.push_back(acentry);
CAccountingEntry &entry = laccentries.back();
wtxOrdered.insert(std::make_pair(entry.nOrderPos, TxPair(nullptr, &entry)));
return true;
}
Amount CWallet::GetRequiredFee(unsigned int nTxBytes) {
return std::max(minTxFee.GetFee(nTxBytes),
::minRelayTxFee.GetFee(nTxBytes));
}
Amount CWallet::GetMinimumFee(unsigned int nTxBytes,
unsigned int nConfirmTarget,
const CTxMemPool &pool) {
// payTxFee is the user-set global for desired feerate.
return GetMinimumFee(nTxBytes, nConfirmTarget, pool,
payTxFee.GetFee(nTxBytes));
}
Amount CWallet::GetMinimumFee(unsigned int nTxBytes,
unsigned int nConfirmTarget,
const CTxMemPool &pool, Amount targetFee) {
Amount nFeeNeeded = targetFee;
// User didn't set: use -txconfirmtarget to estimate...
if (nFeeNeeded == Amount(0)) {
int estimateFoundTarget = nConfirmTarget;
nFeeNeeded = pool.estimateSmartFee(nConfirmTarget, &estimateFoundTarget)
.GetFee(nTxBytes);
// ... unless we don't have enough mempool data for estimatefee, then
// use fallbackFee.
if (nFeeNeeded == Amount(0)) {
nFeeNeeded = fallbackFee.GetFee(nTxBytes);
}
}
// Prevent user from paying a fee below minRelayTxFee or minTxFee.
nFeeNeeded = std::max(nFeeNeeded, GetRequiredFee(nTxBytes));
// But always obey the maximum.
if (nFeeNeeded > maxTxFee) {
nFeeNeeded = maxTxFee;
}
return nFeeNeeded;
}
DBErrors CWallet::LoadWallet(bool &fFirstRunRet) {
fFirstRunRet = false;
DBErrors nLoadWalletRet = CWalletDB(*dbw, "cr+").LoadWallet(this);
if (nLoadWalletRet == DB_NEED_REWRITE) {
if (dbw->Rewrite("\x04pool")) {
LOCK(cs_wallet);
setInternalKeyPool.clear();
setExternalKeyPool.clear();
m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
// This wallet is in its first run if all of these are empty
fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() &&
mapWatchKeys.empty() && setWatchOnly.empty() &&
mapScripts.empty();
if (nLoadWalletRet != DB_LOAD_OK) {
return nLoadWalletRet;
}
uiInterface.LoadWallet(this);
return DB_LOAD_OK;
}
DBErrors CWallet::ZapSelectTx(std::vector &vHashIn,
std::vector &vHashOut) {
AssertLockHeld(cs_wallet); // mapWallet
DBErrors nZapSelectTxRet =
CWalletDB(*dbw, "cr+").ZapSelectTx(vHashIn, vHashOut);
for (uint256 hash : vHashOut) {
mapWallet.erase(hash);
}
if (nZapSelectTxRet == DB_NEED_REWRITE) {
if (dbw->Rewrite("\x04pool")) {
setInternalKeyPool.clear();
setExternalKeyPool.clear();
m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
if (nZapSelectTxRet != DB_LOAD_OK) {
return nZapSelectTxRet;
}
MarkDirty();
return DB_LOAD_OK;
}
DBErrors CWallet::ZapWalletTx(std::vector &vWtx) {
DBErrors nZapWalletTxRet = CWalletDB(*dbw, "cr+").ZapWalletTx(vWtx);
if (nZapWalletTxRet == DB_NEED_REWRITE) {
if (dbw->Rewrite("\x04pool")) {
LOCK(cs_wallet);
setInternalKeyPool.clear();
setExternalKeyPool.clear();
m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
if (nZapWalletTxRet != DB_LOAD_OK) {
return nZapWalletTxRet;
}
return DB_LOAD_OK;
}
bool CWallet::SetAddressBook(const CTxDestination &address,
const std::string &strName,
const std::string &strPurpose) {
bool fUpdated = false;
{
// mapAddressBook
LOCK(cs_wallet);
std::map::iterator mi =
mapAddressBook.find(address);
fUpdated = mi != mapAddressBook.end();
mapAddressBook[address].name = strName;
// Update purpose only if requested.
if (!strPurpose.empty()) {
mapAddressBook[address].purpose = strPurpose;
}
}
NotifyAddressBookChanged(this, address, strName,
::IsMine(*this, address) != ISMINE_NO, strPurpose,
(fUpdated ? CT_UPDATED : CT_NEW));
if (!strPurpose.empty() &&
!CWalletDB(*dbw).WritePurpose(address, strPurpose)) {
return false;
}
return CWalletDB(*dbw).WriteName(address, strName);
}
bool CWallet::DelAddressBook(const CTxDestination &address) {
{
// mapAddressBook
LOCK(cs_wallet);
// Delete destdata tuples associated with address.
for (const std::pair &item :
mapAddressBook[address].destdata) {
CWalletDB(*dbw).EraseDestData(address, item.first);
}
mapAddressBook.erase(address);
}
NotifyAddressBookChanged(this, address, "",
::IsMine(*this, address) != ISMINE_NO, "",
CT_DELETED);
CWalletDB(*dbw).ErasePurpose(address);
return CWalletDB(*dbw).EraseName(address);
}
const std::string &CWallet::GetAccountName(const CScript &scriptPubKey) const {
CTxDestination address;
if (ExtractDestination(scriptPubKey, address) &&
!scriptPubKey.IsUnspendable()) {
auto mi = mapAddressBook.find(address);
if (mi != mapAddressBook.end()) {
return mi->second.name;
}
}
// A scriptPubKey that doesn't have an entry in the address book is
// associated with the default account ("").
const static std::string DEFAULT_ACCOUNT_NAME;
return DEFAULT_ACCOUNT_NAME;
}
/**
* Mark old keypool keys as used, and generate all new keys.
*/
bool CWallet::NewKeyPool() {
LOCK(cs_wallet);
CWalletDB walletdb(*dbw);
for (int64_t nIndex : setInternalKeyPool) {
walletdb.ErasePool(nIndex);
}
setInternalKeyPool.clear();
for (int64_t nIndex : setExternalKeyPool) {
walletdb.ErasePool(nIndex);
}
setExternalKeyPool.clear();
m_pool_key_to_index.clear();
if (!TopUpKeyPool()) {
return false;
}
LogPrintf("CWallet::NewKeyPool rewrote keypool\n");
return true;
}
size_t CWallet::KeypoolCountExternalKeys() {
// setExternalKeyPool
AssertLockHeld(cs_wallet);
return setExternalKeyPool.size();
}
void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) {
AssertLockHeld(cs_wallet);
if (keypool.fInternal) {
setInternalKeyPool.insert(nIndex);
} else {
setExternalKeyPool.insert(nIndex);
}
m_max_keypool_index = std::max(m_max_keypool_index, nIndex);
m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex;
// If no metadata exists yet, create a default with the pool key's
// creation time. Note that this may be overwritten by actually
// stored metadata for that key later, which is fine.
CKeyID keyid = keypool.vchPubKey.GetID();
if (mapKeyMetadata.count(keyid) == 0) {
mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
}
}
bool CWallet::TopUpKeyPool(unsigned int kpSize) {
LOCK(cs_wallet);
if (IsLocked()) {
return false;
}
// Top up key pool
unsigned int nTargetSize;
if (kpSize > 0) {
nTargetSize = kpSize;
} else {
nTargetSize = std::max(
gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), 0);
}
// count amount of available keys (internal, external)
// make sure the keypool of external and internal keys fits the user
// selected target (-keypool)
int64_t missingExternal = std::max(
std::max(nTargetSize, 1) - setExternalKeyPool.size(), 0);
int64_t missingInternal = std::max(
std::max(nTargetSize, 1) - setInternalKeyPool.size(), 0);
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)) {
// don't create extra internal keys
missingInternal = 0;
}
bool internal = false;
CWalletDB walletdb(*dbw);
for (int64_t i = missingInternal + missingExternal; i--;) {
if (i < missingInternal) {
internal = true;
}
// How in the hell did you use so many keys?
assert(m_max_keypool_index < std::numeric_limits::max());
int64_t index = ++m_max_keypool_index;
CPubKey pubkey(GenerateNewKey(walletdb, internal));
if (!walletdb.WritePool(index, CKeyPool(pubkey, internal))) {
throw std::runtime_error(std::string(__func__) +
": writing generated key failed");
}
if (internal) {
setInternalKeyPool.insert(index);
} else {
setExternalKeyPool.insert(index);
}
m_pool_key_to_index[pubkey.GetID()] = index;
}
if (missingInternal + missingExternal > 0) {
LogPrintf(
"keypool added %d keys (%d internal), size=%u (%u internal)\n",
missingInternal + missingExternal, missingInternal,
setInternalKeyPool.size() + setExternalKeyPool.size(),
setInternalKeyPool.size());
}
return true;
}
void CWallet::ReserveKeyFromKeyPool(int64_t &nIndex, CKeyPool &keypool,
bool fRequestedInternal) {
nIndex = -1;
keypool.vchPubKey = CPubKey();
LOCK(cs_wallet);
if (!IsLocked()) {
TopUpKeyPool();
}
bool fReturningInternal = IsHDEnabled() &&
CanSupportFeature(FEATURE_HD_SPLIT) &&
fRequestedInternal;
std::set &setKeyPool =
fReturningInternal ? setInternalKeyPool : setExternalKeyPool;
// Get the oldest key
if (setKeyPool.empty()) {
return;
}
CWalletDB walletdb(*dbw);
auto it = setKeyPool.begin();
nIndex = *it;
setKeyPool.erase(it);
if (!walletdb.ReadPool(nIndex, keypool)) {
throw std::runtime_error(std::string(__func__) + ": read failed");
}
if (!HaveKey(keypool.vchPubKey.GetID())) {
throw std::runtime_error(std::string(__func__) +
": unknown key in key pool");
}
if (keypool.fInternal != fReturningInternal) {
throw std::runtime_error(std::string(__func__) +
": keypool entry misclassified");
}
assert(keypool.vchPubKey.IsValid());
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
LogPrintf("keypool reserve %d\n", nIndex);
}
void CWallet::KeepKey(int64_t nIndex) {
// Remove from key pool.
CWalletDB walletdb(*dbw);
walletdb.ErasePool(nIndex);
LogPrintf("keypool keep %d\n", nIndex);
}
void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey &pubkey) {
// Return to key pool
{
LOCK(cs_wallet);
if (fInternal) {
setInternalKeyPool.insert(nIndex);
} else {
setExternalKeyPool.insert(nIndex);
}
m_pool_key_to_index[pubkey.GetID()] = nIndex;
}
LogPrintf("keypool return %d\n", nIndex);
}
bool CWallet::GetKeyFromPool(CPubKey &result, bool internal) {
CKeyPool keypool;
LOCK(cs_wallet);
int64_t nIndex = 0;
ReserveKeyFromKeyPool(nIndex, keypool, internal);
if (nIndex == -1) {
if (IsLocked()) {
return false;
}
CWalletDB walletdb(*dbw);
result = GenerateNewKey(walletdb, internal);
return true;
}
KeepKey(nIndex);
result = keypool.vchPubKey;
return true;
}
static int64_t GetOldestKeyTimeInPool(const std::set &setKeyPool,
CWalletDB &walletdb) {
if (setKeyPool.empty()) {
return GetTime();
}
CKeyPool keypool;
int64_t nIndex = *(setKeyPool.begin());
if (!walletdb.ReadPool(nIndex, keypool)) {
throw std::runtime_error(std::string(__func__) +
": read oldest key in keypool failed");
}
assert(keypool.vchPubKey.IsValid());
return keypool.nTime;
}
int64_t CWallet::GetOldestKeyPoolTime() {
LOCK(cs_wallet);
CWalletDB walletdb(*dbw);
// load oldest key from keypool, get time and return
int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, walletdb);
if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) {
oldestKey = std::max(
GetOldestKeyTimeInPool(setInternalKeyPool, walletdb), oldestKey);
}
return oldestKey;
}
std::map CWallet::GetAddressBalances() {
std::map balances;
LOCK(cs_wallet);
for (std::pair walletEntry : mapWallet) {
CWalletTx *pcoin = &walletEntry.second;
if (!pcoin->IsTrusted()) {
continue;
}
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) {
continue;
}
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1)) {
continue;
}
for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
CTxDestination addr;
if (!IsMine(pcoin->tx->vout[i])) {
continue;
}
if (!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr)) {
continue;
}
Amount n = IsSpent(walletEntry.first, i)
? Amount(0)
: pcoin->tx->vout[i].nValue;
if (!balances.count(addr)) balances[addr] = Amount(0);
balances[addr] += n;
}
}
return balances;
}
std::set> CWallet::GetAddressGroupings() {
// mapWallet
AssertLockHeld(cs_wallet);
std::set> groupings;
std::set grouping;
for (std::pair walletEntry : mapWallet) {
CWalletTx *pcoin = &walletEntry.second;
if (pcoin->tx->vin.size() > 0) {
bool any_mine = false;
// Group all input addresses with each other.
for (CTxIn txin : pcoin->tx->vin) {
CTxDestination address;
// If this input isn't mine, ignore it.
if (!IsMine(txin)) {
continue;
}
if (!ExtractDestination(mapWallet[txin.prevout.hash]
.tx->vout[txin.prevout.n]
.scriptPubKey,
address)) {
continue;
}
grouping.insert(address);
any_mine = true;
}
// Group change with input addresses.
if (any_mine) {
for (CTxOut txout : pcoin->tx->vout) {
if (IsChange(txout)) {
CTxDestination txoutAddr;
if (!ExtractDestination(txout.scriptPubKey,
txoutAddr)) {
continue;
}
grouping.insert(txoutAddr);
}
}
}
if (grouping.size() > 0) {
groupings.insert(grouping);
grouping.clear();
}
}
// Group lone addrs by themselves.
for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++)
if (IsMine(pcoin->tx->vout[i])) {
CTxDestination address;
if (!ExtractDestination(pcoin->tx->vout[i].scriptPubKey,
address)) {
continue;
}
grouping.insert(address);
groupings.insert(grouping);
grouping.clear();
}
}
// A set of pointers to groups of addresses.
std::set *> uniqueGroupings;
// Map addresses to the unique group containing it.
std::map *> setmap;
for (std::set _grouping : groupings) {
// Make a set of all the groups hit by this new group.
std::set *> hits;
std::map *>::iterator it;
for (CTxDestination address : _grouping) {
if ((it = setmap.find(address)) != setmap.end())
hits.insert((*it).second);
}
// Merge all hit groups into a new single group and delete old groups.
std::set *merged =
new std::set(_grouping);
for (std::set *hit : hits) {
merged->insert(hit->begin(), hit->end());
uniqueGroupings.erase(hit);
delete hit;
}
uniqueGroupings.insert(merged);
// Update setmap.
for (CTxDestination element : *merged) {
setmap[element] = merged;
}
}
std::set> ret;
for (std::set *uniqueGrouping : uniqueGroupings) {
ret.insert(*uniqueGrouping);
delete uniqueGrouping;
}
return ret;
}
std::set
CWallet::GetAccountAddresses(const std::string &strAccount) const {
LOCK(cs_wallet);
std::set result;
for (const std::pair &item :
mapAddressBook) {
const CTxDestination &address = item.first;
const std::string &strName = item.second.name;
if (strName == strAccount) {
result.insert(address);
}
}
return result;
}
bool CReserveKey::GetReservedKey(CPubKey &pubkey, bool internal) {
if (nIndex == -1) {
CKeyPool keypool;
pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal);
if (nIndex != -1) {
vchPubKey = keypool.vchPubKey;
} else {
return false;
}
fInternal = keypool.fInternal;
}
assert(vchPubKey.IsValid());
pubkey = vchPubKey;
return true;
}
void CReserveKey::KeepKey() {
if (nIndex != -1) {
pwallet->KeepKey(nIndex);
}
nIndex = -1;
vchPubKey = CPubKey();
}
void CReserveKey::ReturnKey() {
if (nIndex != -1) {
pwallet->ReturnKey(nIndex, fInternal, vchPubKey);
}
nIndex = -1;
vchPubKey = CPubKey();
}
void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) {
AssertLockHeld(cs_wallet);
bool internal = setInternalKeyPool.count(keypool_id);
if (!internal) assert(setExternalKeyPool.count(keypool_id));
std::set *setKeyPool =
internal ? &setInternalKeyPool : &setExternalKeyPool;
auto it = setKeyPool->begin();
CWalletDB walletdb(*dbw);
while (it != std::end(*setKeyPool)) {
const int64_t &index = *(it);
if (index > keypool_id) {
// set*KeyPool is ordered
break;
}
CKeyPool keypool;
if (walletdb.ReadPool(index, keypool)) {
// TODO: This should be unnecessary
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
}
walletdb.ErasePool(index);
it = setKeyPool->erase(it);
}
}
bool CWallet::HasUnusedKeys(size_t min_keys) const {
return setExternalKeyPool.size() >= min_keys &&
(setInternalKeyPool.size() >= min_keys ||
!CanSupportFeature(FEATURE_HD_SPLIT));
}
void CWallet::GetScriptForMining(std::shared_ptr &script) {
std::shared_ptr rKey = std::make_shared(this);
CPubKey pubkey;
if (!rKey->GetReservedKey(pubkey)) {
return;
}
script = rKey;
script->reserveScript = CScript() << ToByteVector(pubkey) << OP_CHECKSIG;
}
void CWallet::LockCoin(const COutPoint &output) {
// setLockedCoins
AssertLockHeld(cs_wallet);
setLockedCoins.insert(output);
}
void CWallet::UnlockCoin(const COutPoint &output) {
// setLockedCoins
AssertLockHeld(cs_wallet);
setLockedCoins.erase(output);
}
void CWallet::UnlockAllCoins() {
// setLockedCoins
AssertLockHeld(cs_wallet);
setLockedCoins.clear();
}
bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const {
// setLockedCoins
AssertLockHeld(cs_wallet);
COutPoint outpt(hash, n);
return setLockedCoins.count(outpt) > 0;
}
void CWallet::ListLockedCoins(std::vector &vOutpts) {
// setLockedCoins
AssertLockHeld(cs_wallet);
for (std::set::iterator it = setLockedCoins.begin();
it != setLockedCoins.end(); it++) {
COutPoint outpt = (*it);
vOutpts.push_back(outpt);
}
}
/** @} */ // end of Actions
void CWallet::GetKeyBirthTimes(
std::map &mapKeyBirth) const {
// mapKeyMetadata
AssertLockHeld(cs_wallet);
mapKeyBirth.clear();
// Get birth times for keys with metadata.
for (const auto &entry : mapKeyMetadata) {
if (entry.second.nCreateTime) {
mapKeyBirth[entry.first] = entry.second.nCreateTime;
}
}
// Map in which we'll infer heights of other keys the tip can be
// reorganized; use a 144-block safety margin.
CBlockIndex *pindexMax =
chainActive[std::max(0, chainActive.Height() - 144)];
std::map mapKeyFirstBlock;
std::set setKeys;
GetKeys(setKeys);
for (const CKeyID &keyid : setKeys) {
if (mapKeyBirth.count(keyid) == 0) {
mapKeyFirstBlock[keyid] = pindexMax;
}
}
setKeys.clear();
// If there are no such keys, we're done.
if (mapKeyFirstBlock.empty()) {
return;
}
// Find first block that affects those keys, if there are any left.
std::vector vAffected;
for (std::map::const_iterator it = mapWallet.begin();
it != mapWallet.end(); it++) {
// Iterate over all wallet transactions...
const CWalletTx &wtx = (*it).second;
BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock);
if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) {
// ... which are already in a block.
int nHeight = blit->second->nHeight;
for (const CTxOut &txout : wtx.tx->vout) {
// Iterate over all their outputs...
CAffectedKeysVisitor(*this, vAffected)
.Process(txout.scriptPubKey);
for (const CKeyID &keyid : vAffected) {
// ... and all their affected keys.
std::map::iterator rit =
mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() &&
nHeight < rit->second->nHeight) {
rit->second = blit->second;
}
}
vAffected.clear();
}
}
}
// Extract block timestamps for those keys.
for (std::map::const_iterator it =
mapKeyFirstBlock.begin();
it != mapKeyFirstBlock.end(); it++) {
// Block times can be 2h off.
mapKeyBirth[it->first] = it->second->GetBlockTime() - TIMESTAMP_WINDOW;
}
}
/**
* Compute smart timestamp for a transaction being added to the wallet.
*
* Logic:
* - If sending a transaction, assign its timestamp to the current time.
* - If receiving a transaction outside a block, assign its timestamp to the
* current time.
* - If receiving a block with a future timestamp, assign all its (not already
* known) transactions' timestamps to the current time.
* - If receiving a block with a past timestamp, before the most recent known
* transaction (that we care about), assign all its (not already known)
* transactions' timestamps to the same timestamp as that most-recent-known
* transaction.
* - If receiving a block with a past timestamp, but after the most recent known
* transaction, assign all its (not already known) transactions' timestamps to
* the block time.
*
* For more information see CWalletTx::nTimeSmart,
* https://bitcointalk.org/?topic=54527, or
* https://github.com/bitcoin/bitcoin/pull/1393.
*/
unsigned int CWallet::ComputeTimeSmart(const CWalletTx &wtx) const {
unsigned int nTimeSmart = wtx.nTimeReceived;
if (!wtx.hashUnset()) {
if (mapBlockIndex.count(wtx.hashBlock)) {
int64_t latestNow = wtx.nTimeReceived;
int64_t latestEntry = 0;
// Tolerate times up to the last timestamp in the wallet not more
// than 5 minutes into the future
int64_t latestTolerated = latestNow + 300;
const TxItems &txOrdered = wtxOrdered;
for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) {
CWalletTx *const pwtx = it->second.first;
if (pwtx == &wtx) {
continue;
}
CAccountingEntry *const pacentry = it->second.second;
int64_t nSmartTime;
if (pwtx) {
nSmartTime = pwtx->nTimeSmart;
if (!nSmartTime) {
nSmartTime = pwtx->nTimeReceived;
}
} else {
nSmartTime = pacentry->nTime;
}
if (nSmartTime <= latestTolerated) {
latestEntry = nSmartTime;
if (nSmartTime > latestNow) {
latestNow = nSmartTime;
}
break;
}
}
int64_t blocktime = mapBlockIndex[wtx.hashBlock]->GetBlockTime();
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
} else {
LogPrintf("%s: found %s in block %s not in index\n", __func__,
wtx.GetId().ToString(), wtx.hashBlock.ToString());
}
}
return nTimeSmart;
}
bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key,
const std::string &value) {
if (boost::get(&dest)) {
return false;
}
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
return CWalletDB(*dbw).WriteDestData(dest, key, value);
}
bool CWallet::EraseDestData(const CTxDestination &dest,
const std::string &key) {
if (!mapAddressBook[dest].destdata.erase(key)) {
return false;
}
return CWalletDB(*dbw).EraseDestData(dest, key);
}
bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key,
const std::string &value) {
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
return true;
}
bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key,
std::string *value) const {
std::map::const_iterator i =
mapAddressBook.find(dest);
if (i != mapAddressBook.end()) {
CAddressBookData::StringMap::const_iterator j =
i->second.destdata.find(key);
if (j != i->second.destdata.end()) {
if (value) {
*value = j->second;
}
return true;
}
}
return false;
}
std::string CWallet::GetWalletHelpString(bool showDebug) {
std::string strUsage = HelpMessageGroup(_("Wallet options:"));
strUsage += HelpMessageOpt(
"-disablewallet",
_("Do not load the wallet and disable wallet RPC calls"));
strUsage += HelpMessageOpt(
"-keypool=", strprintf(_("Set key pool size to (default: %u)"),
DEFAULT_KEYPOOL_SIZE));
strUsage += HelpMessageOpt(
"-fallbackfee=",
strprintf(_("A fee rate (in %s/kB) that will be used when fee "
"estimation has insufficient data (default: %s)"),
CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)));
strUsage += HelpMessageOpt(
"-mintxfee=",
strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee "
"for transaction creation (default: %s)"),
CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MINFEE)));
strUsage += HelpMessageOpt(
"-paytxfee=",
strprintf(
_("Fee (in %s/kB) to add to transactions you send (default: %s)"),
CURRENCY_UNIT, FormatMoney(payTxFee.GetFeePerK())));
strUsage += HelpMessageOpt(
"-rescan",
_("Rescan the block chain for missing wallet transactions on startup"));
strUsage += HelpMessageOpt(
"-salvagewallet",
_("Attempt to recover private keys from a corrupt wallet on startup"));
- if (showDebug) {
- strUsage += HelpMessageOpt(
- "-sendfreetransactions",
- strprintf(_("Send transactions as zero-fee transactions if "
- "possible (default: %d)"),
- DEFAULT_SEND_FREE_TRANSACTIONS));
- }
strUsage +=
HelpMessageOpt("-spendzeroconfchange",
strprintf(_("Spend unconfirmed change when sending "
"transactions (default: %d)"),
DEFAULT_SPEND_ZEROCONF_CHANGE));
strUsage +=
HelpMessageOpt("-txconfirmtarget=",
strprintf(_("If paytxfee is not set, include enough fee "
"so transactions begin confirmation on "
"average within n blocks (default: %u)"),
DEFAULT_TX_CONFIRM_TARGET));
strUsage += HelpMessageOpt(
"-usehd",
_("Use hierarchical deterministic key generation (HD) after BIP32. "
"Only has effect during wallet creation/first start") +
" " + strprintf(_("(default: %d)"), DEFAULT_USE_HD_WALLET));
strUsage += HelpMessageOpt("-upgradewallet",
_("Upgrade wallet to latest format on startup"));
strUsage +=
HelpMessageOpt("-wallet=",
_("Specify wallet file (within data directory)") + " " +
strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT));
strUsage += HelpMessageOpt(
"-walletbroadcast",
_("Make the wallet broadcast transactions") + " " +
strprintf(_("(default: %d)"), DEFAULT_WALLETBROADCAST));
strUsage += HelpMessageOpt("-walletnotify=",
_("Execute command when a wallet transaction "
"changes (%s in cmd is replaced by TxID)"));
strUsage += HelpMessageOpt(
"-zapwallettxes=",
_("Delete all wallet transactions and only recover those parts of the "
"blockchain through -rescan on startup") +
" " + _("(1 = keep tx meta data e.g. account owner and payment "
"request information, 2 = drop tx meta data)"));
if (showDebug) {
strUsage += HelpMessageGroup(_("Wallet debugging/testing options:"));
strUsage += HelpMessageOpt(
"-dblogsize=",
strprintf("Flush wallet database activity from memory to disk log "
"every megabytes (default: %u)",
DEFAULT_WALLET_DBLOGSIZE));
strUsage += HelpMessageOpt(
"-flushwallet",
strprintf("Run a thread to flush wallet periodically (default: %d)",
DEFAULT_FLUSHWALLET));
strUsage += HelpMessageOpt(
"-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db "
"environment (default: %d)",
DEFAULT_WALLET_PRIVDB));
strUsage += HelpMessageOpt(
"-walletrejectlongchains",
strprintf(_("Wallet will not create transactions that violate "
"mempool chain limits (default: %d)"),
DEFAULT_WALLET_REJECT_LONG_CHAINS));
}
return strUsage;
}
CWallet *CWallet::CreateWalletFromFile(const CChainParams &chainParams,
const std::string walletFile) {
// Needed to restore wallet transaction meta data after -zapwallettxes
std::vector vWtx;
if (gArgs.GetBoolArg("-zapwallettxes", false)) {
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
std::unique_ptr dbw(
new CWalletDBWrapper(&bitdb, walletFile));
CWallet *tempWallet = new CWallet(chainParams, std::move(dbw));
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
if (nZapWalletRet != DB_LOAD_OK) {
InitError(
strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
return nullptr;
}
delete tempWallet;
tempWallet = nullptr;
}
uiInterface.InitMessage(_("Loading wallet..."));
int64_t nStart = GetTimeMillis();
bool fFirstRun = true;
std::unique_ptr dbw(
new CWalletDBWrapper(&bitdb, walletFile));
CWallet *walletInstance = new CWallet(chainParams, std::move(dbw));
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
if (nLoadWalletRet != DB_LOAD_OK) {
if (nLoadWalletRet == DB_CORRUPT) {
InitError(
strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
return nullptr;
}
if (nLoadWalletRet == DB_NONCRITICAL_ERROR) {
InitWarning(strprintf(
_("Error reading %s! All keys read correctly, but transaction "
"data"
" or address book entries might be missing or incorrect."),
walletFile));
} else if (nLoadWalletRet == DB_TOO_NEW) {
InitError(strprintf(
_("Error loading %s: Wallet requires newer version of %s"),
walletFile, _(PACKAGE_NAME)));
return nullptr;
} else if (nLoadWalletRet == DB_NEED_REWRITE) {
InitError(strprintf(
_("Wallet needed to be rewritten: restart %s to complete"),
_(PACKAGE_NAME)));
return nullptr;
} else {
InitError(strprintf(_("Error loading %s"), walletFile));
return nullptr;
}
}
if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) {
int nMaxVersion = gArgs.GetArg("-upgradewallet", 0);
// The -upgradewallet without argument case
if (nMaxVersion == 0) {
LogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
nMaxVersion = CLIENT_VERSION;
// permanently upgrade the wallet immediately
walletInstance->SetMinVersion(FEATURE_LATEST);
} else {
LogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion);
}
if (nMaxVersion < walletInstance->GetVersion()) {
InitError(_("Cannot downgrade wallet"));
return nullptr;
}
walletInstance->SetMaxVersion(nMaxVersion);
}
if (fFirstRun) {
// Create new keyUser and set as default key.
if (gArgs.GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) &&
!walletInstance->IsHDEnabled()) {
// Ensure this wallet.dat can only be opened by clients supporting
// HD with chain split.
walletInstance->SetMinVersion(FEATURE_HD_SPLIT);
// Generate a new master key.
CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey();
if (!walletInstance->SetHDMasterKey(masterPubKey)) {
throw std::runtime_error(std::string(__func__) +
": Storing master key failed");
}
}
// Top up the keypool
if (!walletInstance->TopUpKeyPool()) {
InitError(_("Unable to generate initial keys") += "\n");
return nullptr;
}
walletInstance->SetBestChain(chainActive.GetLocator());
} else if (gArgs.IsArgSet("-usehd")) {
bool useHD = gArgs.GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET);
if (walletInstance->IsHDEnabled() && !useHD) {
InitError(strprintf(_("Error loading %s: You can't disable HD on a "
"already existing HD wallet"),
walletFile));
return nullptr;
}
if (!walletInstance->IsHDEnabled() && useHD) {
InitError(strprintf(_("Error loading %s: You can't enable HD on a "
"already existing non-HD wallet"),
walletFile));
return nullptr;
}
}
LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart);
RegisterValidationInterface(walletInstance);
// Try to top up keypool. No-op if the wallet is locked.
walletInstance->TopUpKeyPool();
CBlockIndex *pindexRescan = chainActive.Genesis();
if (!gArgs.GetBoolArg("-rescan", false)) {
CWalletDB walletdb(*walletInstance->dbw);
CBlockLocator locator;
if (walletdb.ReadBestBlock(locator)) {
pindexRescan = FindForkInGlobalIndex(chainActive, locator);
}
}
if (chainActive.Tip() && chainActive.Tip() != pindexRescan) {
// We can't rescan beyond non-pruned blocks, stop and throw an error.
// This might happen if a user uses a old wallet within a pruned node or
// if he ran -disablewallet for a longer time, then decided to
// re-enable.
if (fPruneMode) {
CBlockIndex *block = chainActive.Tip();
while (block && block->pprev &&
(block->pprev->nStatus & BLOCK_HAVE_DATA) &&
block->pprev->nTx > 0 && pindexRescan != block) {
block = block->pprev;
}
if (pindexRescan != block) {
InitError(_("Prune: last wallet synchronisation goes beyond "
"pruned data. You need to -reindex (download the "
"whole blockchain again in case of pruned node)"));
return nullptr;
}
}
uiInterface.InitMessage(_("Rescanning..."));
LogPrintf("Rescanning last %i blocks (from block %i)...\n",
chainActive.Height() - pindexRescan->nHeight,
pindexRescan->nHeight);
nStart = GetTimeMillis();
walletInstance->ScanForWalletTransactions(pindexRescan, true);
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
walletInstance->SetBestChain(chainActive.GetLocator());
walletInstance->dbw->IncrementUpdateCounter();
// Restore wallet transaction metadata after -zapwallettxes=1
if (gArgs.GetBoolArg("-zapwallettxes", false) &&
gArgs.GetArg("-zapwallettxes", "1") != "2") {
CWalletDB walletdb(*walletInstance->dbw);
for (const CWalletTx &wtxOld : vWtx) {
uint256 txid = wtxOld.GetId();
std::map::iterator mi =
walletInstance->mapWallet.find(txid);
if (mi != walletInstance->mapWallet.end()) {
const CWalletTx *copyFrom = &wtxOld;
CWalletTx *copyTo = &mi->second;
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
copyTo->nTimeReceived = copyFrom->nTimeReceived;
copyTo->nTimeSmart = copyFrom->nTimeSmart;
copyTo->fFromMe = copyFrom->fFromMe;
copyTo->strFromAccount = copyFrom->strFromAccount;
copyTo->nOrderPos = copyFrom->nOrderPos;
walletdb.WriteTx(*copyTo);
}
}
}
}
walletInstance->SetBroadcastTransactions(
gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
LOCK(walletInstance->cs_wallet);
LogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
LogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
LogPrintf("mapAddressBook.size() = %u\n",
walletInstance->mapAddressBook.size());
return walletInstance;
}
bool CWallet::InitLoadWallet(const CChainParams &chainParams) {
if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
LogPrintf("Wallet disabled!\n");
return true;
}
for (const std::string &walletFile : gArgs.GetArgs("-wallet")) {
CWallet *const pwallet = CreateWalletFromFile(chainParams, walletFile);
if (!pwallet) {
return false;
}
vpwallets.push_back(pwallet);
}
return true;
}
std::atomic CWallet::fFlushScheduled(false);
void CWallet::postInitProcess(CScheduler &scheduler) {
// Add wallet transactions that aren't already in a block to mempool.
// Do this here as mempool requires genesis block to be loaded.
ReacceptWalletTransactions();
// Run a thread to flush wallet periodically.
if (!CWallet::fFlushScheduled.exchange(true)) {
scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
}
}
bool CWallet::ParameterInteraction() {
gArgs.SoftSetArg("-wallet", DEFAULT_WALLET_DAT);
const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1;
if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
return true;
}
if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) &&
gArgs.SoftSetBoolArg("-walletbroadcast", false)) {
LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting "
"-walletbroadcast=0\n",
__func__);
}
if (gArgs.GetBoolArg("-salvagewallet", false) &&
gArgs.SoftSetBoolArg("-rescan", true)) {
if (is_multiwallet) {
return InitError(
strprintf("%s is only allowed with a single wallet file",
"-salvagewallet"));
}
// Rewrite just private keys: rescan to find transactions
LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting "
"-rescan=1\n",
__func__);
}
int zapwallettxes = gArgs.GetArg("-zapwallettxes", 0);
// -zapwallettxes implies dropping the mempool on startup
if (zapwallettxes != 0 && gArgs.SoftSetBoolArg("-persistmempool", false)) {
LogPrintf("%s: parameter interaction: -zapwallettxes=%s -> setting "
"-persistmempool=0\n",
__func__, zapwallettxes);
}
// -zapwallettxes implies a rescan
if (zapwallettxes != 0) {
if (is_multiwallet) {
return InitError(
strprintf("%s is only allowed with a single wallet file",
"-zapwallettxes"));
}
if (gArgs.SoftSetBoolArg("-rescan", true)) {
LogPrintf("%s: parameter interaction: -zapwallettxes=%s -> setting "
"-rescan=1\n",
__func__, zapwallettxes);
}
LogPrintf("%s: parameter interaction: -zapwallettxes= -> setting "
"-rescan=1\n",
__func__);
}
if (is_multiwallet) {
if (gArgs.GetBoolArg("-upgradewallet", false)) {
return InitError(
strprintf("%s is only allowed with a single wallet file",
"-upgradewallet"));
}
}
if (gArgs.GetBoolArg("-sysperms", false)) {
return InitError("-sysperms is not allowed in combination with enabled "
"wallet functionality");
}
if (gArgs.GetArg("-prune", 0) && gArgs.GetBoolArg("-rescan", false)) {
return InitError(
_("Rescans are not possible in pruned mode. You will need to use "
"-reindex which will download the whole blockchain again."));
}
if (::minRelayTxFee.GetFeePerK() > HIGH_TX_FEE_PER_KB) {
InitWarning(
AmountHighWarn("-minrelaytxfee") + " " +
_("The wallet will avoid paying less than the minimum relay fee."));
}
if (gArgs.IsArgSet("-mintxfee")) {
Amount n(0);
auto parsed = ParseMoney(gArgs.GetArg("-mintxfee", ""), n);
if (!parsed || Amount(0) == n) {
return InitError(
AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", "")));
}
if (n > HIGH_TX_FEE_PER_KB) {
InitWarning(AmountHighWarn("-mintxfee") + " " +
_("This is the minimum transaction fee you pay on "
"every transaction."));
}
CWallet::minTxFee = CFeeRate(n);
}
if (gArgs.IsArgSet("-fallbackfee")) {
Amount nFeePerK(0);
if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) {
return InitError(
strprintf(_("Invalid amount for -fallbackfee=: '%s'"),
gArgs.GetArg("-fallbackfee", "")));
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
InitWarning(AmountHighWarn("-fallbackfee") + " " +
_("This is the transaction fee you may pay when fee "
"estimates are not available."));
}
CWallet::fallbackFee = CFeeRate(nFeePerK);
}
if (gArgs.IsArgSet("-paytxfee")) {
Amount nFeePerK(0);
if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) {
return InitError(
AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", "")));
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
InitWarning(AmountHighWarn("-paytxfee") + " " +
_("This is the transaction fee you will pay if you "
"send a transaction."));
}
payTxFee = CFeeRate(nFeePerK, 1000);
if (payTxFee < ::minRelayTxFee) {
return InitError(strprintf(
_("Invalid amount for -paytxfee=: '%s' (must "
"be at least %s)"),
gArgs.GetArg("-paytxfee", ""), ::minRelayTxFee.ToString()));
}
}
if (gArgs.IsArgSet("-maxtxfee")) {
Amount nMaxFee(0);
if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) {
return InitError(
AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")));
}
if (nMaxFee > HIGH_MAX_TX_FEE) {
InitWarning(_("-maxtxfee is set very high! Fees this large could "
"be paid on a single transaction."));
}
maxTxFee = nMaxFee;
if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee) {
return InitError(strprintf(
_("Invalid amount for -maxtxfee=: '%s' (must "
"be at least the minrelay fee of %s to prevent "
"stuck transactions)"),
gArgs.GetArg("-maxtxfee", ""), ::minRelayTxFee.ToString()));
}
}
nTxConfirmTarget =
gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
bSpendZeroConfChange =
gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
- fSendFreeTransactions = gArgs.GetBoolArg("-sendfreetransactions",
- DEFAULT_SEND_FREE_TRANSACTIONS);
-
- if (fSendFreeTransactions &&
- gArgs.GetArg("-limitfreerelay", DEFAULT_LIMITFREERELAY) <= 0) {
- return InitError("Creation of free transactions with their relay "
- "disabled is not supported.");
- }
return true;
}
bool CWallet::BackupWallet(const std::string &strDest) {
return dbw->Backup(strDest);
}
CKeyPool::CKeyPool() {
nTime = GetTime();
fInternal = false;
}
CKeyPool::CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn) {
nTime = GetTime();
vchPubKey = vchPubKeyIn;
fInternal = internalIn;
}
CWalletKey::CWalletKey(int64_t nExpires) {
nTimeCreated = (nExpires ? GetTime() : 0);
nTimeExpires = nExpires;
}
void CMerkleTx::SetMerkleBranch(const CBlockIndex *pindex, int posInBlock) {
// Update the tx's hashBlock
hashBlock = pindex->GetBlockHash();
// Set the position of the transaction in the block.
nIndex = posInBlock;
}
int CMerkleTx::GetDepthInMainChain(const CBlockIndex *&pindexRet) const {
if (hashUnset()) {
return 0;
}
AssertLockHeld(cs_main);
// Find the block it claims to be in.
BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
if (mi == mapBlockIndex.end()) {
return 0;
}
CBlockIndex *pindex = (*mi).second;
if (!pindex || !chainActive.Contains(pindex)) {
return 0;
}
pindexRet = pindex;
return ((nIndex == -1) ? (-1) : 1) *
(chainActive.Height() - pindex->nHeight + 1);
}
int CMerkleTx::GetBlocksToMaturity() const {
if (!IsCoinBase()) {
return 0;
}
return std::max(0, (COINBASE_MATURITY + 1) - GetDepthInMainChain());
}
bool CMerkleTx::AcceptToMemoryPool(const Amount nAbsurdFee,
CValidationState &state) {
return ::AcceptToMemoryPool(GetConfig(), mempool, state, tx, true, nullptr,
false, nAbsurdFee);
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index e34e8422f..f0c0a7ad9 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1,1225 +1,1222 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_WALLET_H
#define BITCOIN_WALLET_WALLET_H
#include "amount.h"
#include "script/ismine.h"
#include "script/sign.h"
#include "streams.h"
#include "tinyformat.h"
#include "ui_interface.h"
#include "utilstrencodings.h"
#include "validationinterface.h"
#include "wallet/crypter.h"
#include "wallet/rpcwallet.h"
#include "wallet/walletdb.h"
#include
#include
#include
#include
#include