diff options
Diffstat (limited to 'src')
39 files changed, 887 insertions, 509 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 4b65774fc6..4fbd605d9e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -133,6 +133,7 @@ BITCOIN_CORE_H = \ rpc/safemode.h \ rpc/server.h \ rpc/register.h \ + rpc/util.h \ scheduler.h \ script/sigcache.h \ script/sign.h \ @@ -352,6 +353,7 @@ libbitcoin_util_a_SOURCES = \ fs.cpp \ random.cpp \ rpc/protocol.cpp \ + rpc/util.cpp \ support/cleanse.cpp \ sync.cpp \ threadinterrupt.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 97a6c346ab..39757baf9e 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -124,13 +124,17 @@ public: assert(consensus.hashGenesisBlock == uint256S("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")); assert(genesis.hashMerkleRoot == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")); - // Note that of those with the service bits flag, most only support a subset of possible options - vSeeds.emplace_back("seed.bitcoin.sipa.be", true); // Pieter Wuille, only supports x1, x5, x9, and xd - vSeeds.emplace_back("dnsseed.bluematt.me", true); // Matt Corallo, only supports x9 - vSeeds.emplace_back("dnsseed.bitcoin.dashjr.org", false); // Luke Dashjr - vSeeds.emplace_back("seed.bitcoinstats.com", true); // Christian Decker, supports x1 - xf - vSeeds.emplace_back("seed.bitcoin.jonasschnelli.ch", true); // Jonas Schnelli, only supports x1, x5, x9, and xd - vSeeds.emplace_back("seed.btc.petertodd.org", true); // Peter Todd, only supports x1, x5, x9, and xd + // Note that of those which support the service bits prefix, most only support a subset of + // possible options. + // This is fine at runtime as we'll fall back to using them as a oneshot if they dont support the + // service bits we want, but we should get them updated to support all service bits wanted by any + // release ASAP to avoid it where possible. + vSeeds.emplace_back("seed.bitcoin.sipa.be"); // Pieter Wuille, only supports x1, x5, x9, and xd + vSeeds.emplace_back("dnsseed.bluematt.me"); // Matt Corallo, only supports x9 + vSeeds.emplace_back("dnsseed.bitcoin.dashjr.org"); // Luke Dashjr + vSeeds.emplace_back("seed.bitcoinstats.com"); // Christian Decker, supports x1 - xf + vSeeds.emplace_back("seed.bitcoin.jonasschnelli.ch"); // Jonas Schnelli, only supports x1, x5, x9, and xd + vSeeds.emplace_back("seed.btc.petertodd.org"); // Peter Todd, only supports x1, x5, x9, and xd base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,0); base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,5); @@ -229,10 +233,10 @@ public: vFixedSeeds.clear(); vSeeds.clear(); // nodes with support for servicebits filtering should be at the top - vSeeds.emplace_back("testnet-seed.bitcoin.jonasschnelli.ch", true); - vSeeds.emplace_back("seed.tbtc.petertodd.org", true); - vSeeds.emplace_back("seed.testnet.bitcoin.sprovoost.nl", true); - vSeeds.emplace_back("testnet-seed.bluematt.me", false); + vSeeds.emplace_back("testnet-seed.bitcoin.jonasschnelli.ch"); + vSeeds.emplace_back("seed.tbtc.petertodd.org"); + vSeeds.emplace_back("seed.testnet.bitcoin.sprovoost.nl"); + vSeeds.emplace_back("testnet-seed.bluematt.me"); // Just a static list of stable node(s), only supports x9 base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,111); base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,196); diff --git a/src/chainparams.h b/src/chainparams.h index ecfe1a0ac3..d478da9891 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -14,12 +14,6 @@ #include <memory> #include <vector> -struct CDNSSeedData { - std::string host; - bool supportsServiceBitsFiltering; - CDNSSeedData(const std::string &strHost, bool supportsServiceBitsFilteringIn) : host(strHost), supportsServiceBitsFiltering(supportsServiceBitsFilteringIn) {} -}; - struct SeedSpec6 { uint8_t addr[16]; uint16_t port; @@ -71,7 +65,8 @@ public: bool MineBlocksOnDemand() const { return fMineBlocksOnDemand; } /** Return the BIP70 network string (main, test or regtest) */ std::string NetworkIDString() const { return strNetworkID; } - const std::vector<CDNSSeedData>& DNSSeeds() const { return vSeeds; } + /** Return the list of hostnames to look up for DNS seeds */ + const std::vector<std::string>& DNSSeeds() const { return vSeeds; } const std::vector<unsigned char>& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } const std::string& Bech32HRP() const { return bech32_hrp; } const std::vector<SeedSpec6>& FixedSeeds() const { return vFixedSeeds; } @@ -85,7 +80,7 @@ protected: CMessageHeader::MessageStartChars pchMessageStart; int nDefaultPort; uint64_t nPruneAfterHeight; - std::vector<CDNSSeedData> vSeeds; + std::vector<std::string> vSeeds; std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES]; std::string bech32_hrp; std::string strNetworkID; diff --git a/src/compat.h b/src/compat.h index 65e9683e2f..aae84b1181 100644 --- a/src/compat.h +++ b/src/compat.h @@ -33,7 +33,7 @@ #include <ws2tcpip.h> #include <stdint.h> #else -#include <sys/fcntl.h> +#include <fcntl.h> #include <sys/mman.h> #include <sys/select.h> #include <sys/socket.h> diff --git a/src/init.cpp b/src/init.cpp index 7215e87359..d1d733af94 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1143,23 +1143,10 @@ bool AppInitParameterInteraction() static bool LockDataDirectory(bool probeOnly) { - std::string strDataDir = GetDataDir().string(); - // Make sure only a single Bitcoin process is using the data directory. - fs::path pathLockFile = GetDataDir() / ".lock"; - FILE* file = fsbridge::fopen(pathLockFile, "a"); // empty lock file; created if it doesn't exist. - if (file) fclose(file); - - try { - static boost::interprocess::file_lock lock(pathLockFile.string().c_str()); - if (!lock.try_lock()) { - return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running."), strDataDir, _(PACKAGE_NAME))); - } - if (probeOnly) { - lock.unlock(); - } - } catch(const boost::interprocess::interprocess_exception& e) { - return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running.") + " %s.", strDataDir, _(PACKAGE_NAME), e.what())); + fs::path datadir = GetDataDir(); + if (!LockDirectory(datadir, ".lock", probeOnly)) { + return InitError(strprintf(_("Cannot obtain a lock on data directory %s. %s is probably already running."), datadir.string(), _(PACKAGE_NAME))); } return true; } @@ -1223,6 +1210,15 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("Using config file %s\n", GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)).string()); LogPrintf("Using at most %i automatic connections (%i file descriptors available)\n", nMaxConnections, nFD); + // Warn about relative -datadir path. + if (gArgs.IsArgSet("-datadir") && !fs::path(gArgs.GetArg("-datadir", "")).is_absolute()) { + LogPrintf("Warning: relative datadir option '%s' specified, which will be interpreted relative to the " + "current working directory '%s'. This is fragile, because if bitcoin is started in the future " + "from a different location, it will be unable to locate the current data files. There could " + "also be data loss if bitcoin is started while in a temporary directory.\n", + gArgs.GetArg("-datadir", ""), fs::current_path().string()); + } + InitSignatureCache(); InitScriptExecutionCache(); diff --git a/src/miner.cpp b/src/miner.cpp index 4e63ab4df0..dda52790c6 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -352,7 +352,7 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda // Try to compare the mapTx entry to the mapModifiedTx entry iter = mempool.mapTx.project<0>(mi); if (modit != mapModifiedTx.get<ancestor_score>().end() && - CompareModifiedEntry()(*modit, CTxMemPoolModifiedEntry(iter))) { + CompareTxMemPoolEntryByAncestorFee()(*modit, CTxMemPoolModifiedEntry(iter))) { // The best entry in mapModifiedTx has higher score // than the one from mapTx. // Switch which transaction (package) to consider diff --git a/src/miner.h b/src/miner.h index 698b4a4788..9c086332d4 100644 --- a/src/miner.h +++ b/src/miner.h @@ -41,6 +41,12 @@ struct CTxMemPoolModifiedEntry { nSigOpCostWithAncestors = entry->GetSigOpCostWithAncestors(); } + int64_t GetModifiedFee() const { return iter->GetModifiedFee(); } + uint64_t GetSizeWithAncestors() const { return nSizeWithAncestors; } + CAmount GetModFeesWithAncestors() const { return nModFeesWithAncestors; } + size_t GetTxSize() const { return iter->GetTxSize(); } + const CTransaction& GetTx() const { return iter->GetTx(); } + CTxMemPool::txiter iter; uint64_t nSizeWithAncestors; CAmount nModFeesWithAncestors; @@ -67,21 +73,6 @@ struct modifiedentry_iter { } }; -// This matches the calculation in CompareTxMemPoolEntryByAncestorFee, -// except operating on CTxMemPoolModifiedEntry. -// TODO: refactor to avoid duplication of this logic. -struct CompareModifiedEntry { - bool operator()(const CTxMemPoolModifiedEntry &a, const CTxMemPoolModifiedEntry &b) const - { - double f1 = (double)a.nModFeesWithAncestors * b.nSizeWithAncestors; - double f2 = (double)b.nModFeesWithAncestors * a.nSizeWithAncestors; - if (f1 == f2) { - return CTxMemPool::CompareIteratorByHash()(a.iter, b.iter); - } - return f1 > f2; - } -}; - // A comparator that sorts transactions based on number of ancestors. // This is sufficient to sort an ancestor package in an order that is valid // to appear in a block. @@ -106,7 +97,7 @@ typedef boost::multi_index_container< // Reuse same tag from CTxMemPool's similar index boost::multi_index::tag<ancestor_score>, boost::multi_index::identity<CTxMemPoolModifiedEntry>, - CompareModifiedEntry + CompareTxMemPoolEntryByAncestorFee > > > indexed_modified_transaction_set; diff --git a/src/net.cpp b/src/net.cpp index 682f47bff2..8111390749 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -136,7 +136,7 @@ static std::vector<CAddress> convertSeed6(const std::vector<SeedSpec6> &vSeedsIn for (const auto& seed_in : vSeedsIn) { struct in6_addr ip; memcpy(&ip, seed_in.addr, sizeof(ip)); - CAddress addr(CService(ip, seed_in.port), NODE_NETWORK); + CAddress addr(CService(ip, seed_in.port), GetDesirableServiceFlags(NODE_NONE)); addr.nTime = GetTime() - GetRand(nOneWeek) - nOneWeek; vSeedsOut.push_back(addr); } @@ -1577,19 +1577,6 @@ void MapPort(bool) -static std::string GetDNSHost(const CDNSSeedData& data, ServiceFlags* requiredServiceBits) -{ - //use default host for non-filter-capable seeds or if we use the default service bits (NODE_NETWORK) - if (!data.supportsServiceBitsFiltering || *requiredServiceBits == NODE_NETWORK) { - *requiredServiceBits = NODE_NETWORK; - return data.host; - } - - // See chainparams.cpp, most dnsseeds only support one or two possible servicebits hostnames - return strprintf("x%x.%s", *requiredServiceBits, data.host); -} - - void CConnman::ThreadDNSAddressSeed() { // goal: only query DNS seeds if address need is acute @@ -1612,22 +1599,22 @@ void CConnman::ThreadDNSAddressSeed() } } - const std::vector<CDNSSeedData> &vSeeds = Params().DNSSeeds(); + const std::vector<std::string> &vSeeds = Params().DNSSeeds(); int found = 0; LogPrintf("Loading addresses from DNS seeds (could take a while)\n"); - for (const CDNSSeedData &seed : vSeeds) { + for (const std::string &seed : vSeeds) { if (interruptNet) { return; } if (HaveNameProxy()) { - AddOneShot(seed.host); + AddOneShot(seed); } else { std::vector<CNetAddr> vIPs; std::vector<CAddress> vAdd; ServiceFlags requiredServiceBits = GetDesirableServiceFlags(NODE_NONE); - std::string host = GetDNSHost(seed, &requiredServiceBits); + std::string host = strprintf("x%x.%s", requiredServiceBits, seed); CNetAddr resolveSource; if (!resolveSource.SetInternal(host)) { continue; @@ -1643,6 +1630,10 @@ void CConnman::ThreadDNSAddressSeed() found++; } addrman.Add(vAdd, resolveSource); + } else { + // We now avoid directly using results from DNS Seeds which do not support service bit filtering, + // instead using them as a oneshot to get nodes with our desired service bits. + AddOneShot(seed); } } } diff --git a/src/protocol.h b/src/protocol.h index 452b5b28ce..43d8ac067a 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -291,7 +291,15 @@ enum ServiceFlags : uint64_t { * unless they set NODE_NETWORK_LIMITED and we are out of IBD, in which * case NODE_NETWORK_LIMITED suffices). * - * Thus, generally, avoid calling with peerServices == NODE_NONE. + * Thus, generally, avoid calling with peerServices == NODE_NONE, unless + * state-specific flags must absolutely be avoided. When called with + * peerServices == NODE_NONE, the returned desirable service flags are + * guaranteed to not change dependant on state - ie they are suitable for + * use when describing peers which we know to be desirable, but for which + * we do not have a confirmed set of service flags. + * + * If the NODE_NONE return value is changed, contrib/seeds/makeseeds.py + * should be updated appropriately to filter for the same nodes. */ static ServiceFlags GetDesirableServiceFlags(ServiceFlags services) { return ServiceFlags(NODE_NETWORK | NODE_WITNESS); diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index a2521a1e9e..74f0db3520 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -341,7 +341,7 @@ void AddressTableModel::updateEntry(const QString &address, priv->updateEntry(address, label, isMine, purpose, status); } -QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address) +QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type) { std::string strLabel = label.toStdString(); std::string strAddress = address.toStdString(); @@ -384,8 +384,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con return QString(); } } - wallet->LearnRelatedScripts(newKey, g_address_type); - strAddress = EncodeDestination(GetDestinationForKey(newKey, g_address_type)); + wallet->LearnRelatedScripts(newKey, address_type); + strAddress = EncodeDestination(GetDestinationForKey(newKey, address_type)); } else { diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index d04b95ebae..11439e25d5 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -8,6 +8,8 @@ #include <QAbstractTableModel> #include <QStringList> +enum OutputType : int; + class AddressTablePriv; class WalletModel; @@ -61,7 +63,7 @@ public: /* Add an address to the model. Returns the added address on success, and an empty string otherwise. */ - QString addRow(const QString &type, const QString &label, const QString &address); + QString addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type); /* Look up label for address in address book, if not found return empty string. */ diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp index 6b7df2f2b9..a945fc6aa0 100644 --- a/src/qt/editaddressdialog.cpp +++ b/src/qt/editaddressdialog.cpp @@ -11,6 +11,8 @@ #include <QDataWidgetMapper> #include <QMessageBox> +extern OutputType g_address_type; + EditAddressDialog::EditAddressDialog(Mode _mode, QWidget *parent) : QDialog(parent), ui(new Ui::EditAddressDialog), @@ -77,7 +79,8 @@ bool EditAddressDialog::saveCurrentRow() address = model->addRow( mode == NewSendingAddress ? AddressTableModel::Send : AddressTableModel::Receive, ui->labelEdit->text(), - ui->addressEdit->text()); + ui->addressEdit->text(), + g_address_type); break; case EditReceivingAddress: case EditSendingAddress: diff --git a/src/qt/forms/receivecoinsdialog.ui b/src/qt/forms/receivecoinsdialog.ui index 58f030ebf0..09fb435a58 100644 --- a/src/qt/forms/receivecoinsdialog.ui +++ b/src/qt/forms/receivecoinsdialog.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>776</width> - <height>364</height> + <height>396</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1"> @@ -28,6 +28,22 @@ <layout class="QVBoxLayout" name="verticalLayout_3"> <item> <layout class="QGridLayout" name="gridLayout"> + <item row="5" column="0"> + <widget class="QLabel" name="label"> + <property name="toolTip"> + <string>An optional amount to request. Leave this empty or zero to not request a specific amount.</string> + </property> + <property name="text"> + <string>&Amount:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>reqAmount</cstring> + </property> + </widget> + </item> <item row="6" column="0"> <widget class="QLabel" name="label_3"> <property name="toolTip"> @@ -51,13 +67,6 @@ </property> </widget> </item> - <item row="6" column="2"> - <widget class="QLineEdit" name="reqMessage"> - <property name="toolTip"> - <string>An optional message to attach to the payment request, which will be displayed when the request is opened. Note: The message will not be sent with the payment over the Bitcoin network.</string> - </property> - </widget> - </item> <item row="2" column="2"> <widget class="QLabel" name="label_5"> <property name="text"> @@ -81,32 +90,10 @@ </property> </widget> </item> - <item row="5" column="0"> - <widget class="QLabel" name="label"> - <property name="toolTip"> - <string>An optional amount to request. Leave this empty or zero to not request a specific amount.</string> - </property> - <property name="text"> - <string>&Amount:</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - <property name="buddy"> - <cstring>reqAmount</cstring> - </property> - </widget> - </item> - <item row="5" column="2"> - <widget class="BitcoinAmountField" name="reqAmount"> - <property name="minimumSize"> - <size> - <width>80</width> - <height>0</height> - </size> - </property> + <item row="6" column="2"> + <widget class="QLineEdit" name="reqMessage"> <property name="toolTip"> - <string>An optional amount to request. Leave this empty or zero to not request a specific amount.</string> + <string>An optional message to attach to the payment request, which will be displayed when the request is opened. Note: The message will not be sent with the payment over the Bitcoin network.</string> </property> </widget> </item> @@ -174,6 +161,73 @@ </property> </widget> </item> + <item row="5" column="2"> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="BitcoinAmountField" name="reqAmount"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>1000</width> + <height>100</height> + </size> + </property> + <property name="toolTip"> + <string>An optional amount to request. Leave this empty or zero to not request a specific amount.</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="useBech32"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>1000</width> + <height>16777215</height> + </size> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Bech32 addresses (BIP-173) are cheaper to spend from and offer better protection against typos. When unchecked a P2SH wrapped SegWit address will be created, compatible with older wallets.</string> + </property> + <property name="text"> + <string>Generate Bech32 address</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> </layout> </item> </layout> @@ -306,6 +360,7 @@ <tabstops> <tabstop>reqLabel</tabstop> <tabstop>reqAmount</tabstop> + <tabstop>useBech32</tabstop> <tabstop>reqMessage</tabstop> <tabstop>receiveButton</tabstop> <tabstop>clearButton</tabstop> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 670d6108db..558d4f108c 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -995,6 +995,18 @@ QString formatBytes(uint64_t bytes) return QString(QObject::tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024); } +qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize, qreal font_size) { + while(font_size >= minPointSize) { + font.setPointSizeF(font_size); + QFontMetrics fm(font); + if (fm.width(text) < width) { + break; + } + font_size -= 0.5; + } + return font_size; +} + void ClickableLabel::mouseReleaseEvent(QMouseEvent *event) { Q_EMIT clicked(event->pos()); diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index ad0e22ccd6..71a69483f5 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -201,6 +201,8 @@ namespace GUIUtil QString formatBytes(uint64_t bytes); + qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize = 4, qreal startPointSize = 14); + class ClickableLabel : public QLabel { Q_OBJECT diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index e07f2b74ee..7fd5285467 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -2,6 +2,8 @@ // 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 <qt/receivecoinsdialog.h> #include <qt/forms/ui_receivecoinsdialog.h> @@ -91,6 +93,15 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) SLOT(recentRequestsView_selectionChanged(QItemSelection, QItemSelection))); // Last 2 columns are set by the columnResizingFixer, when the table geometry is ready. columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this); + + // configure bech32 checkbox, disable if launched with legacy as default: + if (model->getDefaultAddressType() == OUTPUT_TYPE_BECH32) { + ui->useBech32->setCheckState(Qt::Checked); + } else { + ui->useBech32->setCheckState(Qt::Unchecked); + } + + ui->useBech32->setVisible(model->getDefaultAddressType() != OUTPUT_TYPE_LEGACY); } } @@ -133,7 +144,11 @@ void ReceiveCoinsDialog::on_receiveButton_clicked() QString address; QString label = ui->reqLabel->text(); /* Generate new receiving address */ - address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, ""); + OutputType address_type = model->getDefaultAddressType(); + if (address_type != OUTPUT_TYPE_LEGACY) { + address_type = ui->useBech32->isChecked() ? OUTPUT_TYPE_BECH32 : OUTPUT_TYPE_P2SH_SEGWIT; + } + address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", address_type); SendCoinsRecipient info(address, label, ui->reqAmount->value(), ui->reqMessage->text()); ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this); diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index 209397ca0c..d4cb0e5ba2 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -183,9 +183,13 @@ void ReceiveRequestDialog::update() QPainter painter(&qrAddrImage); painter.drawImage(0, 0, qrImage.scaled(QR_IMAGE_SIZE, QR_IMAGE_SIZE)); QFont font = GUIUtil::fixedPitchFont(); - font.setPixelSize(12); - painter.setFont(font); QRect paddedRect = qrAddrImage.rect(); + + // calculate ideal font size + qreal font_size = GUIUtil::calculateIdealFontSize(paddedRect.width() - 20, info.address, font); + font.setPointSizeF(font_size); + + painter.setFont(font); paddedRect.setHeight(QR_IMAGE_SIZE+12); painter.drawText(paddedRect, Qt::AlignBottom|Qt::AlignCenter, info.address); painter.end(); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index a270e5de59..cd49292138 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -169,7 +169,9 @@ void TestGUI() } { LOCK(cs_main); - wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, true); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + wallet.ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true); } wallet.SetBroadcastTransactions(true); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 494b46905d..4d7e977fcb 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -733,6 +733,11 @@ bool WalletModel::hdEnabled() const return wallet->IsHDEnabled(); } +OutputType WalletModel::getDefaultAddressType() const +{ + return g_address_type; +} + int WalletModel::getDefaultConfirmTarget() const { return nTxConfirmTarget; diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 7d7e4f160e..9e13de79be 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -15,6 +15,8 @@ #include <QObject> +enum OutputType : int; + class AddressTableModel; class OptionsModel; class PlatformStyle; @@ -214,6 +216,8 @@ public: bool hdEnabled() const; + OutputType getDefaultAddressType() const; + int getDefaultConfirmTarget() const; private: diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 1b2c71c4a4..346672e45a 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -24,6 +24,7 @@ #include <util.h> #include <utilstrencodings.h> #include <hash.h> +#include <validationinterface.h> #include <warnings.h> #include <stdint.h> @@ -323,6 +324,21 @@ UniValue waitforblockheight(const JSONRPCRequest& request) return ret; } +UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() > 0) { + throw std::runtime_error( + "syncwithvalidationinterfacequeue\n" + "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n" + "\nExamples:\n" + + HelpExampleCli("syncwithvalidationinterfacequeue","") + + HelpExampleRpc("syncwithvalidationinterfacequeue","") + ); + } + SyncWithValidationInterfaceQueue(); + return NullUniValue; +} + UniValue getdifficulty(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) @@ -1628,6 +1644,7 @@ static const CRPCCommand commands[] = { "hidden", "waitfornewblock", &waitfornewblock, {"timeout"} }, { "hidden", "waitforblock", &waitforblock, {"blockhash","timeout"} }, { "hidden", "waitforblockheight", &waitforblockheight, {"height","timeout"} }, + { "hidden", "syncwithvalidationinterfacequeue", &syncwithvalidationinterfacequeue, {} }, }; void RegisterBlockchainRPCCommands(CRPCTable &t) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 95a27d474b..c11dda22c4 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -15,6 +15,7 @@ #include <netbase.h> #include <rpc/blockchain.h> #include <rpc/server.h> +#include <rpc/util.h> #include <timedata.h> #include <util.h> #include <utilstrencodings.h> @@ -254,88 +255,21 @@ UniValue validateaddress(const JSONRPCRequest& request) // Needed even with !ENABLE_WALLET, to pass (ignored) pointers around class CWallet; -/** - * Used by addmultisigaddress / createmultisig: - */ -CScript _createmultisig_redeemScript(CWallet * const pwallet, const UniValue& params) -{ - int nRequired = params[0].get_int(); - const UniValue& keys = params[1].get_array(); - - // Gather public keys - if (nRequired < 1) - throw std::runtime_error("a multisignature address must require at least one key to redeem"); - if ((int)keys.size() < nRequired) - throw std::runtime_error( - strprintf("not enough keys supplied " - "(got %u keys, but need at least %d to redeem)", keys.size(), nRequired)); - if (keys.size() > 16) - throw std::runtime_error("Number of addresses involved in the multisignature address creation > 16\nReduce the number"); - std::vector<CPubKey> pubkeys; - pubkeys.resize(keys.size()); - for (unsigned int i = 0; i < keys.size(); i++) - { - const std::string& ks = keys[i].get_str(); -#ifdef ENABLE_WALLET - // Case 1: Bitcoin address and we have full public key: - CTxDestination dest = DecodeDestination(ks); - if (pwallet && IsValidDestination(dest)) { - CKeyID key = GetKeyForDestination(*pwallet, dest); - if (key.IsNull()) { - throw std::runtime_error(strprintf("%s does not refer to a key", ks)); - } - CPubKey vchPubKey; - if (!pwallet->GetPubKey(key, vchPubKey)) { - throw std::runtime_error(strprintf("no full public key for address %s", ks)); - } - if (!vchPubKey.IsFullyValid()) - throw std::runtime_error(" Invalid public key: "+ks); - pubkeys[i] = vchPubKey; - } - - // Case 2: hex public key - else -#endif - if (IsHex(ks)) - { - CPubKey vchPubKey(ParseHex(ks)); - if (!vchPubKey.IsFullyValid()) - throw std::runtime_error(" Invalid public key: "+ks); - pubkeys[i] = vchPubKey; - } - else - { - throw std::runtime_error(" Invalid public key: "+ks); - } - } - CScript result = GetScriptForMultisig(nRequired, pubkeys); - - if (result.size() > MAX_SCRIPT_ELEMENT_SIZE) - throw std::runtime_error( - strprintf("redeemScript exceeds size limit: %d > %d", result.size(), MAX_SCRIPT_ELEMENT_SIZE)); - - return result; -} - UniValue createmultisig(const JSONRPCRequest& request) { -#ifdef ENABLE_WALLET - CWallet * const pwallet = GetWalletForJSONRPCRequest(request); -#else - CWallet * const pwallet = nullptr; -#endif - if (request.fHelp || request.params.size() < 2 || request.params.size() > 2) { std::string msg = "createmultisig nrequired [\"key\",...]\n" "\nCreates a multi-signature address with n signature of m keys required.\n" "It returns a json object with the address and redeemScript.\n" - + "DEPRECATION WARNING: Using addresses with createmultisig is deprecated. Clients must\n" + "transition to using addmultisigaddress to create multisig addresses with addresses known\n" + "to the wallet before upgrading to v0.17. To use the deprecated functionality, start bitcoind with -deprecatedrpc=createmultisig\n" "\nArguments:\n" - "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n" - "2. \"keys\" (string, required) A json array of keys which are bitcoin addresses or hex-encoded public keys\n" + "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n" + "2. \"keys\" (string, required) A json array of hex-encoded public keys\n" " [\n" - " \"key\" (string) bitcoin address or hex-encoded public key\n" + " \"key\" (string) The hex-encoded public key\n" " ,...\n" " ]\n" @@ -346,16 +280,37 @@ UniValue createmultisig(const JSONRPCRequest& request) "}\n" "\nExamples:\n" - "\nCreate a multisig address from 2 addresses\n" - + HelpExampleCli("createmultisig", "2 \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + + "\nCreate a multisig address from 2 public keys\n" + + HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") + "\nAs a json rpc call\n" - + HelpExampleRpc("createmultisig", "2, \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + + HelpExampleRpc("createmultisig", "2, \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") ; throw std::runtime_error(msg); } + int required = request.params[0].get_int(); + + // Get the public keys + const UniValue& keys = request.params[1].get_array(); + std::vector<CPubKey> pubkeys; + for (unsigned int i = 0; i < keys.size(); ++i) { + if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) { + pubkeys.push_back(HexToPubKey(keys[i].get_str())); + } else { +#ifdef ENABLE_WALLET + CWallet* const pwallet = GetWalletForJSONRPCRequest(request); + if (IsDeprecatedRPCEnabled("createmultisig") && EnsureWalletIsAvailable(pwallet, false)) { + pubkeys.push_back(AddrToPubKey(pwallet, keys[i].get_str())); + } else +#endif + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\nNote that from v0.16, createmultisig no longer accepts addresses." + " Clients must transition to using addmultisigaddress to create multisig addresses with addresses known to the wallet before upgrading to v0.17." + " To use the deprecated functionality, start bitcoind with -deprecatedrpc=createmultisig", keys[i].get_str())); + } + } + // Construct using pay-to-script-hash: - CScript inner = _createmultisig_redeemScript(pwallet, request.params); + CScript inner = CreateMultisigRedeemscript(required, pubkeys); CScriptID innerID(inner); UniValue result(UniValue::VOBJ); @@ -668,6 +623,18 @@ UniValue echo(const JSONRPCRequest& request) return request.params; } +static UniValue getinfo_deprecated(const JSONRPCRequest& request) +{ + throw JSONRPCError(RPC_METHOD_NOT_FOUND, + "getinfo\n" + "\nThis call was removed in version 0.16.0. Use the appropriate fields from:\n" + "- getblockchaininfo: blocks, difficulty, chain\n" + "- getnetworkinfo: version, protocolversion, timeoffset, connections, proxy, relayfee, warnings\n" + "- getwalletinfo: balance, keypoololdest, keypoolsize, paytxfee, unlocked_until, walletversion\n" + "\nbitcoin-cli has the option -getinfo to collect and format these in the old format." + ); +} + static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- @@ -682,6 +649,7 @@ static const CRPCCommand commands[] = { "hidden", "setmocktime", &setmocktime, {"timestamp"}}, { "hidden", "echo", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, { "hidden", "echojson", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, + { "hidden", "getinfo", &getinfo_deprecated, {}}, }; void RegisterMiscRPCCommands(CRPCTable &t) diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp new file mode 100644 index 0000000000..09ded4e46e --- /dev/null +++ b/src/rpc/util.cpp @@ -0,0 +1,68 @@ +// Copyright (c) 2017 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 <base58.h> +#include <keystore.h> +#include <pubkey.h> +#include <rpc/protocol.h> +#include <rpc/util.h> +#include <tinyformat.h> +#include <utilstrencodings.h> + +// Converts a hex string to a public key if possible +CPubKey HexToPubKey(const std::string& hex_in) +{ + if (!IsHex(hex_in)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in); + } + CPubKey vchPubKey(ParseHex(hex_in)); + if (!vchPubKey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in); + } + return vchPubKey; +} + +// Retrieves a public key for an address from the given CKeyStore +CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in) +{ + CTxDestination dest = DecodeDestination(addr_in); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address: " + addr_in); + } + CKeyID key = GetKeyForDestination(*keystore, dest); + if (key.IsNull()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("%s does not refer to a key", addr_in)); + } + CPubKey vchPubKey; + if (!keystore->GetPubKey(key, vchPubKey)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("no full public key for address %s", addr_in)); + } + if (!vchPubKey.IsFullyValid()) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet contains an invalid public key"); + } + return vchPubKey; +} + +// Creates a multisig redeemscript from a given list of public keys and number required. +CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey>& pubkeys) +{ + // Gather public keys + if (required < 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "a multisignature address must require at least one key to redeem"); + } + if ((int)pubkeys.size() < required) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("not enough keys supplied (got %u keys, but need at least %d to redeem)", pubkeys.size(), required)); + } + if (pubkeys.size() > 16) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Number of keys involved in the multisignature address creation > 16\nReduce the number"); + } + + CScript result = GetScriptForMultisig(required, pubkeys); + + if (result.size() > MAX_SCRIPT_ELEMENT_SIZE) { + throw JSONRPCError(RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", result.size(), MAX_SCRIPT_ELEMENT_SIZE))); + } + + return result; +} diff --git a/src/rpc/util.h b/src/rpc/util.h new file mode 100644 index 0000000000..568a4260ba --- /dev/null +++ b/src/rpc/util.h @@ -0,0 +1,19 @@ +// Copyright (c) 2017 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_RPC_UTIL_H +#define BITCOIN_RPC_UTIL_H + +#include <string> +#include <vector> + +class CKeyStore; +class CPubKey; +class CScript; + +CPubKey HexToPubKey(const std::string& hex_in); +CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in); +CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey>& pubkeys); + +#endif // BITCOIN_RPC_UTIL_H diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index f56f341498..1766c6a093 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -287,35 +287,6 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest) pool.removeRecursive(pool.mapTx.find(tx9.GetHash())->GetTx()); pool.removeRecursive(pool.mapTx.find(tx8.GetHash())->GetTx()); - /* Now check the sort on the mining score index. - * Final order should be: - * - * tx7 (2M) - * tx2 (20k) - * tx4 (15000) - * tx1/tx5 (10000) - * tx3/6 (0) - * (Ties resolved by hash) - */ - sortedOrder.clear(); - sortedOrder.push_back(tx7.GetHash().ToString()); - sortedOrder.push_back(tx2.GetHash().ToString()); - sortedOrder.push_back(tx4.GetHash().ToString()); - if (tx1.GetHash() < tx5.GetHash()) { - sortedOrder.push_back(tx5.GetHash().ToString()); - sortedOrder.push_back(tx1.GetHash().ToString()); - } else { - sortedOrder.push_back(tx1.GetHash().ToString()); - sortedOrder.push_back(tx5.GetHash().ToString()); - } - if (tx3.GetHash() < tx6.GetHash()) { - sortedOrder.push_back(tx6.GetHash().ToString()); - sortedOrder.push_back(tx3.GetHash().ToString()); - } else { - sortedOrder.push_back(tx3.GetHash().ToString()); - sortedOrder.push_back(tx6.GetHash().ToString()); - } - CheckSort<mining_score>(pool, sortedOrder); } BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) @@ -427,6 +398,23 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest) sortedOrder.erase(sortedOrder.end()-2); sortedOrder.insert(sortedOrder.begin(), tx7.GetHash().ToString()); CheckSort<ancestor_score>(pool, sortedOrder); + + // High-fee parent, low-fee child + // tx7 -> tx8 + CMutableTransaction tx8 = CMutableTransaction(); + tx8.vin.resize(1); + tx8.vin[0].prevout = COutPoint(tx7.GetHash(), 0); + tx8.vin[0].scriptSig = CScript() << OP_11; + tx8.vout.resize(1); + tx8.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; + tx8.vout[0].nValue = 10*COIN; + + // Check that we sort by min(feerate, ancestor_feerate): + // set the fee so that the ancestor feerate is above tx1/5, + // but the transaction's own feerate is lower + pool.addUnchecked(tx8.GetHash(), entry.Fee(5000LL).FromTx(tx8)); + sortedOrder.insert(sortedOrder.end()-1, tx8.GetHash().ToString()); + CheckSort<ancestor_score>(pool, sortedOrder); } diff --git a/src/txmempool.cpp b/src/txmempool.cpp index ffb024aef9..d1edde284f 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -906,8 +906,8 @@ bool CCoinsViewMemPool::GetCoin(const COutPoint &outpoint, Coin &coin) const { size_t CTxMemPool::DynamicMemoryUsage() const { LOCK(cs); - // Estimate the overhead of mapTx to be 15 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented. - return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage; + // Estimate the overhead of mapTx to be 12 pointers + an allocation, as no exact formula for boost::multi_index_contained is implemented. + return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 12 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage; } void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason) { diff --git a/src/txmempool.h b/src/txmempool.h index 512e70f8fa..d6f8e7094b 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -206,18 +206,14 @@ class CompareTxMemPoolEntryByDescendantScore public: bool operator()(const CTxMemPoolEntry& a, const CTxMemPoolEntry& b) const { - bool fUseADescendants = UseDescendantScore(a); - bool fUseBDescendants = UseDescendantScore(b); + double a_mod_fee, a_size, b_mod_fee, b_size; - double aModFee = fUseADescendants ? a.GetModFeesWithDescendants() : a.GetModifiedFee(); - double aSize = fUseADescendants ? a.GetSizeWithDescendants() : a.GetTxSize(); - - double bModFee = fUseBDescendants ? b.GetModFeesWithDescendants() : b.GetModifiedFee(); - double bSize = fUseBDescendants ? b.GetSizeWithDescendants() : b.GetTxSize(); + GetModFeeAndSize(a, a_mod_fee, a_size); + GetModFeeAndSize(b, b_mod_fee, b_size); // Avoid division by rewriting (a/b > c/d) as (a*d > c*b). - double f1 = aModFee * bSize; - double f2 = aSize * bModFee; + double f1 = a_mod_fee * b_size; + double f2 = a_size * b_mod_fee; if (f1 == f2) { return a.GetTime() >= b.GetTime(); @@ -225,12 +221,21 @@ public: return f1 < f2; } - // Calculate which score to use for an entry (avoiding division). - bool UseDescendantScore(const CTxMemPoolEntry &a) const + // Return the fee/size we're using for sorting this entry. + void GetModFeeAndSize(const CTxMemPoolEntry &a, double &mod_fee, double &size) const { + // Compare feerate with descendants to feerate of the transaction, and + // return the fee/size for the max. double f1 = (double)a.GetModifiedFee() * a.GetSizeWithDescendants(); double f2 = (double)a.GetModFeesWithDescendants() * a.GetTxSize(); - return f2 > f1; + + if (f2 > f1) { + mod_fee = a.GetModFeesWithDescendants(); + size = a.GetSizeWithDescendants(); + } else { + mod_fee = a.GetModifiedFee(); + size = a.GetTxSize(); + } } }; @@ -261,33 +266,53 @@ public: } }; +/** \class CompareTxMemPoolEntryByAncestorScore + * + * Sort an entry by min(score/size of entry's tx, score/size with all ancestors). + */ class CompareTxMemPoolEntryByAncestorFee { public: - bool operator()(const CTxMemPoolEntry& a, const CTxMemPoolEntry& b) const + template<typename T> + bool operator()(const T& a, const T& b) const { - double aFees = a.GetModFeesWithAncestors(); - double aSize = a.GetSizeWithAncestors(); + double a_mod_fee, a_size, b_mod_fee, b_size; - double bFees = b.GetModFeesWithAncestors(); - double bSize = b.GetSizeWithAncestors(); + GetModFeeAndSize(a, a_mod_fee, a_size); + GetModFeeAndSize(b, b_mod_fee, b_size); // Avoid division by rewriting (a/b > c/d) as (a*d > c*b). - double f1 = aFees * bSize; - double f2 = aSize * bFees; + double f1 = a_mod_fee * b_size; + double f2 = a_size * b_mod_fee; if (f1 == f2) { return a.GetTx().GetHash() < b.GetTx().GetHash(); } - return f1 > f2; } + + // Return the fee/size we're using for sorting this entry. + template <typename T> + void GetModFeeAndSize(const T &a, double &mod_fee, double &size) const + { + // Compare feerate with ancestors to feerate of the transaction, and + // return the fee/size for the min. + double f1 = (double)a.GetModifiedFee() * a.GetSizeWithAncestors(); + double f2 = (double)a.GetModFeesWithAncestors() * a.GetTxSize(); + + if (f1 > f2) { + mod_fee = a.GetModFeesWithAncestors(); + size = a.GetSizeWithAncestors(); + } else { + mod_fee = a.GetModifiedFee(); + size = a.GetTxSize(); + } + } }; // Multi_index tag names struct descendant_score {}; struct entry_time {}; -struct mining_score {}; struct ancestor_score {}; class CBlockPolicyEstimator; @@ -356,7 +381,6 @@ public: * - transaction hash * - feerate [we use max(feerate of tx, feerate of tx with all descendants)] * - time in mempool - * - mining score (feerate modified by any fee deltas from PrioritiseTransaction) * * Note: the term "descendant" refers to in-mempool transactions that depend on * this one, while "ancestor" refers to in-mempool transactions that a given @@ -446,12 +470,6 @@ public: boost::multi_index::identity<CTxMemPoolEntry>, CompareTxMemPoolEntryByEntryTime >, - // sorted by score (for mining prioritization) - boost::multi_index::ordered_unique< - boost::multi_index::tag<mining_score>, - boost::multi_index::identity<CTxMemPoolEntry>, - CompareTxMemPoolEntryByScore - >, // sorted by fee rate with ancestors boost::multi_index::ordered_non_unique< boost::multi_index::tag<ancestor_score>, diff --git a/src/util.cpp b/src/util.cpp index 150bc503df..80eed24ffd 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -72,6 +72,7 @@ #include <boost/algorithm/string/case_conv.hpp> // for to_lower() #include <boost/algorithm/string/predicate.hpp> // for startswith() and endswith() +#include <boost/interprocess/sync/file_lock.hpp> #include <boost/program_options/detail/config_file.hpp> #include <boost/thread.hpp> #include <openssl/crypto.h> @@ -375,6 +376,27 @@ int LogPrintStr(const std::string &str) return ret; } +bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only) +{ + fs::path pathLockFile = directory / lockfile_name; + FILE* file = fsbridge::fopen(pathLockFile, "a"); // empty lock file; created if it doesn't exist. + if (file) fclose(file); + + try { + static std::map<std::string, boost::interprocess::file_lock> locks; + boost::interprocess::file_lock& lock = locks.emplace(pathLockFile.string(), pathLockFile.string().c_str()).first->second; + if (!lock.try_lock()) { + return false; + } + if (probe_only) { + lock.unlock(); + } + } catch (const boost::interprocess::interprocess_exception& e) { + return error("Error while attempting to lock directory %s: %s", directory.string(), e.what()); + } + return true; +} + /** Interpret string as boolean, for argument parsing */ static bool InterpretBool(const std::string& strValue) { diff --git a/src/util.h b/src/util.h index 6a0d6a31e7..277b4c66af 100644 --- a/src/util.h +++ b/src/util.h @@ -173,6 +173,7 @@ bool TruncateFile(FILE *file, unsigned int length); int RaiseFileDescriptorLimit(int nMinFD); void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length); bool RenameOver(fs::path src, fs::path dest); +bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only=false); bool TryCreateDirectories(const fs::path& p); fs::path GetDefaultDataDir(); const fs::path &GetDataDir(bool fNetSpecific = true); diff --git a/src/validation.cpp b/src/validation.cpp index 14d60bb269..8cee0dfac3 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1121,7 +1121,13 @@ bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus: bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams) { - if (!ReadBlockFromDisk(block, pindex->GetBlockPos(), consensusParams)) + CDiskBlockPos blockPos; + { + LOCK(cs_main); + blockPos = pindex->GetBlockPos(); + } + + if (!ReadBlockFromDisk(block, blockPos, consensusParams)) return false; if (block.GetHash() != pindex->GetBlockHash()) return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s", diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 35ff0e1eec..23c6279128 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -95,7 +95,7 @@ void CDBEnv::Close() EnvShutdown(); } -bool CDBEnv::Open(const fs::path& pathIn) +bool CDBEnv::Open(const fs::path& pathIn, bool retry) { if (fDbEnvInit) return true; @@ -103,6 +103,11 @@ bool CDBEnv::Open(const fs::path& pathIn) boost::this_thread::interruption_point(); strPath = pathIn.string(); + if (!LockDirectory(pathIn, ".walletlock")) { + LogPrintf("Cannot obtain a lock on wallet directory %s. Another instance of bitcoin may be using it.\n", strPath); + return false; + } + fs::path pathLogDir = pathIn / "database"; TryCreateDirectories(pathLogDir); fs::path pathErrorFile = pathIn / "db.log"; @@ -134,7 +139,24 @@ bool CDBEnv::Open(const fs::path& pathIn) S_IRUSR | S_IWUSR); if (ret != 0) { dbenv->close(0); - return error("CDBEnv::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret)); + LogPrintf("CDBEnv::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret)); + if (retry) { + // try moving the database env out of the way + fs::path pathDatabaseBak = pathIn / strprintf("database.%d.bak", GetTime()); + try { + fs::rename(pathLogDir, pathDatabaseBak); + LogPrintf("Moved old %s to %s. Retrying.\n", pathLogDir.string(), pathDatabaseBak.string()); + } catch (const fs::filesystem_error&) { + // failure is ok (well, not really, but it's not worse than what we started with) + } + // try opening it again one more time + if (!Open(pathIn, false)) { + // if it still fails, it probably means we can't even create the database env + return false; + } + } else { + return false; + } } fDbEnvInit = true; @@ -269,25 +291,11 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walle return false; } - if (!bitdb.Open(walletDir)) - { - // try moving the database env out of the way - fs::path pathDatabase = walletDir / "database"; - fs::path pathDatabaseBak = walletDir / strprintf("database.%d.bak", GetTime()); - try { - fs::rename(pathDatabase, pathDatabaseBak); - LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string()); - } catch (const fs::filesystem_error&) { - // failure is ok (well, not really, but it's not worse than what we started with) - } - - // try again - if (!bitdb.Open(walletDir)) { - // if it still fails, it probably means we can't even create the database env - errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir); - return false; - } + if (!bitdb.Open(walletDir, true)) { + errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir); + return false; } + return true; } diff --git a/src/wallet/db.h b/src/wallet/db.h index c6f317927f..787135e400 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -68,7 +68,7 @@ public: typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair; bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult); - bool Open(const fs::path& path); + bool Open(const fs::path& path, bool retry = 0); void Close(); void Flush(bool fShutdown); void CheckpointLSN(const std::string& strFile); diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 0f453f154f..2d26f7ae0f 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -205,11 +205,15 @@ bool VerifyWallets() return true; } - if (gArgs.IsArgSet("-walletdir") && !fs::is_directory(GetWalletDir())) { - if (fs::exists(fs::system_complete(gArgs.GetArg("-walletdir", "")))) { - return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), gArgs.GetArg("-walletdir", "").c_str())); + if (gArgs.IsArgSet("-walletdir")) { + fs::path wallet_dir = gArgs.GetArg("-walletdir", ""); + if (!fs::exists(wallet_dir)) { + return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string())); + } else if (!fs::is_directory(wallet_dir)) { + return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string())); + } else if (!wallet_dir.is_absolute()) { + return InitError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string())); } - return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), gArgs.GetArg("-walletdir", "").c_str())); } LogPrintf("Using wallet directory %s\n", GetWalletDir().string()); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 645e776b68..0b021f9fe0 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -86,7 +86,8 @@ UniValue importprivkey(const JSONRPCRequest& request) "1. \"privkey\" (string, required) The private key (see dumpprivkey)\n" "2. \"label\" (string, optional, default=\"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" - "\nNote: This call can take minutes to complete if rescan is true.\n" + "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" "\nExamples:\n" "\nDump a private key\n" + HelpExampleCli("dumpprivkey", "\"myaddress\"") + @@ -101,61 +102,65 @@ UniValue importprivkey(const JSONRPCRequest& request) ); - LOCK2(cs_main, pwallet->cs_wallet); - - EnsureWalletIsUnlocked(pwallet); - - std::string strSecret = request.params[0].get_str(); - std::string strLabel = ""; - if (!request.params[1].isNull()) - strLabel = request.params[1].get_str(); - - // Whether to perform rescan after import + WalletRescanReserver reserver(pwallet); bool fRescan = true; - if (!request.params[2].isNull()) - fRescan = request.params[2].get_bool(); - - if (fRescan && fPruneMode) - throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); + { + LOCK2(cs_main, pwallet->cs_wallet); - CBitcoinSecret vchSecret; - bool fGood = vchSecret.SetString(strSecret); + EnsureWalletIsUnlocked(pwallet); - if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); + std::string strSecret = request.params[0].get_str(); + std::string strLabel = ""; + if (!request.params[1].isNull()) + strLabel = request.params[1].get_str(); - CKey key = vchSecret.GetKey(); - if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); + // Whether to perform rescan after import + if (!request.params[2].isNull()) + fRescan = request.params[2].get_bool(); - CPubKey pubkey = key.GetPubKey(); - assert(key.VerifyPubKey(pubkey)); - CKeyID vchAddress = pubkey.GetID(); - { - pwallet->MarkDirty(); + if (fRescan && fPruneMode) + throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); - // We don't know which corresponding address will be used; label them all - for (const auto& dest : GetAllDestinationsForKey(pubkey)) { - pwallet->SetAddressBook(dest, strLabel, "receive"); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } - // Don't throw error in case a key is already there - if (pwallet->HaveKey(vchAddress)) { - return NullUniValue; - } + CBitcoinSecret vchSecret; + bool fGood = vchSecret.SetString(strSecret); - pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1; + if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); - if (!pwallet->AddKeyPubKey(key, pubkey)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); - } - pwallet->LearnAllRelatedScripts(pubkey); + CKey key = vchSecret.GetKey(); + if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); - // whenever a key is imported, we need to scan the whole chain - pwallet->UpdateTimeFirstKey(1); + CPubKey pubkey = key.GetPubKey(); + assert(key.VerifyPubKey(pubkey)); + CKeyID vchAddress = pubkey.GetID(); + { + pwallet->MarkDirty(); + // We don't know which corresponding address will be used; label them all + for (const auto& dest : GetAllDestinationsForKey(pubkey)) { + pwallet->SetAddressBook(dest, strLabel, "receive"); + } - if (fRescan) { - pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */); + // Don't throw error in case a key is already there + if (pwallet->HaveKey(vchAddress)) { + return NullUniValue; + } + + // whenever a key is imported, we need to scan the whole chain + pwallet->UpdateTimeFirstKey(1); + pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1; + + if (!pwallet->AddKeyPubKey(key, pubkey)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + } + pwallet->LearnAllRelatedScripts(pubkey); } } + if (fRescan) { + pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); + } return NullUniValue; } @@ -237,7 +242,8 @@ UniValue importaddress(const JSONRPCRequest& request) "2. \"label\" (string, optional, default=\"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" "4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n" - "\nNote: This call can take minutes to complete if rescan is true.\n" + "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" "If you have the full public key, you should call importpubkey instead of this.\n" "\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n" "as change, and not show up in many RPCs.\n" @@ -263,29 +269,35 @@ UniValue importaddress(const JSONRPCRequest& request) if (fRescan && fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); + WalletRescanReserver reserver(pwallet); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + // Whether to import a p2sh version, too bool fP2SH = false; if (!request.params[3].isNull()) fP2SH = request.params[3].get_bool(); - LOCK2(cs_main, pwallet->cs_wallet); + { + LOCK2(cs_main, pwallet->cs_wallet); - CTxDestination dest = DecodeDestination(request.params[0].get_str()); - if (IsValidDestination(dest)) { - if (fP2SH) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); + CTxDestination dest = DecodeDestination(request.params[0].get_str()); + if (IsValidDestination(dest)) { + if (fP2SH) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); + } + ImportAddress(pwallet, dest, strLabel); + } else if (IsHex(request.params[0].get_str())) { + std::vector<unsigned char> data(ParseHex(request.params[0].get_str())); + ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH); + } else { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); } - ImportAddress(pwallet, dest, strLabel); - } else if (IsHex(request.params[0].get_str())) { - std::vector<unsigned char> data(ParseHex(request.params[0].get_str())); - ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH); - } else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); } - if (fRescan) { - pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */); + pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); pwallet->ReacceptWalletTransactions(); } @@ -406,7 +418,8 @@ UniValue importpubkey(const JSONRPCRequest& request) "1. \"pubkey\" (string, required) The hex-encoded public key\n" "2. \"label\" (string, optional, default=\"\") An optional label\n" "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n" - "\nNote: This call can take minutes to complete if rescan is true.\n" + "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" "\nExamples:\n" "\nImport a public key with rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\"") + @@ -429,6 +442,11 @@ UniValue importpubkey(const JSONRPCRequest& request) if (fRescan && fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); + WalletRescanReserver reserver(pwallet); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } + if (!IsHex(request.params[0].get_str())) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); std::vector<unsigned char> data(ParseHex(request.params[0].get_str())); @@ -436,17 +454,18 @@ UniValue importpubkey(const JSONRPCRequest& request) if (!pubKey.IsFullyValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); - LOCK2(cs_main, pwallet->cs_wallet); + { + LOCK2(cs_main, pwallet->cs_wallet); - for (const auto& dest : GetAllDestinationsForKey(pubKey)) { - ImportAddress(pwallet, dest, strLabel); + for (const auto& dest : GetAllDestinationsForKey(pubKey)) { + ImportAddress(pwallet, dest, strLabel); + } + ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false); + pwallet->LearnAllRelatedScripts(pubKey); } - ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false); - pwallet->LearnAllRelatedScripts(pubKey); - if (fRescan) { - pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */); + pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */); pwallet->ReacceptWalletTransactions(); } @@ -479,91 +498,98 @@ UniValue importwallet(const JSONRPCRequest& request) if (fPruneMode) throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode"); - LOCK2(cs_main, pwallet->cs_wallet); + WalletRescanReserver reserver(pwallet); + if (!reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } - EnsureWalletIsUnlocked(pwallet); + int64_t nTimeBegin = 0; + bool fGood = true; + { + LOCK2(cs_main, pwallet->cs_wallet); - std::ifstream file; - file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate); - if (!file.is_open()) - throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); + EnsureWalletIsUnlocked(pwallet); - int64_t nTimeBegin = chainActive.Tip()->GetBlockTime(); + std::ifstream file; + file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate); + if (!file.is_open()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); + } + nTimeBegin = chainActive.Tip()->GetBlockTime(); - bool fGood = true; + int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); + file.seekg(0, file.beg); - int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); - file.seekg(0, file.beg); - - pwallet->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI - while (file.good()) { - pwallet->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100)))); - std::string line; - std::getline(file, line); - if (line.empty() || line[0] == '#') - continue; - - std::vector<std::string> vstr; - boost::split(vstr, line, boost::is_any_of(" ")); - if (vstr.size() < 2) - continue; - CBitcoinSecret vchSecret; - if (vchSecret.SetString(vstr[0])) { - CKey key = vchSecret.GetKey(); - CPubKey pubkey = key.GetPubKey(); - assert(key.VerifyPubKey(pubkey)); - CKeyID keyid = pubkey.GetID(); - if (pwallet->HaveKey(keyid)) { - LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid)); + pwallet->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI + while (file.good()) { + pwallet->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100)))); + std::string line; + std::getline(file, line); + if (line.empty() || line[0] == '#') continue; - } - int64_t nTime = DecodeDumpTime(vstr[1]); - std::string strLabel; - bool fLabel = true; - for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { - if (boost::algorithm::starts_with(vstr[nStr], "#")) - break; - if (vstr[nStr] == "change=1") - fLabel = false; - if (vstr[nStr] == "reserve=1") - fLabel = false; - if (boost::algorithm::starts_with(vstr[nStr], "label=")) { - strLabel = DecodeDumpString(vstr[nStr].substr(6)); - fLabel = true; - } - } - LogPrintf("Importing %s...\n", EncodeDestination(keyid)); - if (!pwallet->AddKeyPubKey(key, pubkey)) { - fGood = false; + + std::vector<std::string> vstr; + boost::split(vstr, line, boost::is_any_of(" ")); + if (vstr.size() < 2) continue; + CBitcoinSecret vchSecret; + if (vchSecret.SetString(vstr[0])) { + CKey key = vchSecret.GetKey(); + CPubKey pubkey = key.GetPubKey(); + assert(key.VerifyPubKey(pubkey)); + CKeyID keyid = pubkey.GetID(); + if (pwallet->HaveKey(keyid)) { + LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid)); + continue; + } + int64_t nTime = DecodeDumpTime(vstr[1]); + std::string strLabel; + bool fLabel = true; + for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { + if (boost::algorithm::starts_with(vstr[nStr], "#")) + break; + if (vstr[nStr] == "change=1") + fLabel = false; + if (vstr[nStr] == "reserve=1") + fLabel = false; + if (boost::algorithm::starts_with(vstr[nStr], "label=")) { + strLabel = DecodeDumpString(vstr[nStr].substr(6)); + fLabel = true; + } + } + LogPrintf("Importing %s...\n", EncodeDestination(keyid)); + if (!pwallet->AddKeyPubKey(key, pubkey)) { + fGood = false; + continue; + } + pwallet->mapKeyMetadata[keyid].nCreateTime = nTime; + if (fLabel) + pwallet->SetAddressBook(keyid, strLabel, "receive"); + nTimeBegin = std::min(nTimeBegin, nTime); + } else if(IsHex(vstr[0])) { + std::vector<unsigned char> vData(ParseHex(vstr[0])); + CScript script = CScript(vData.begin(), vData.end()); + if (pwallet->HaveCScript(script)) { + LogPrintf("Skipping import of %s (script already present)\n", vstr[0]); + continue; + } + if(!pwallet->AddCScript(script)) { + LogPrintf("Error importing script %s\n", vstr[0]); + fGood = false; + continue; + } + int64_t birth_time = DecodeDumpTime(vstr[1]); + if (birth_time > 0) { + pwallet->m_script_metadata[CScriptID(script)].nCreateTime = birth_time; + nTimeBegin = std::min(nTimeBegin, birth_time); + } } - pwallet->mapKeyMetadata[keyid].nCreateTime = nTime; - if (fLabel) - pwallet->SetAddressBook(keyid, strLabel, "receive"); - nTimeBegin = std::min(nTimeBegin, nTime); - } else if(IsHex(vstr[0])) { - std::vector<unsigned char> vData(ParseHex(vstr[0])); - CScript script = CScript(vData.begin(), vData.end()); - if (pwallet->HaveCScript(script)) { - LogPrintf("Skipping import of %s (script already present)\n", vstr[0]); - continue; - } - if(!pwallet->AddCScript(script)) { - LogPrintf("Error importing script %s\n", vstr[0]); - fGood = false; - continue; - } - int64_t birth_time = DecodeDumpTime(vstr[1]); - if (birth_time > 0) { - pwallet->m_script_metadata[CScriptID(script)].nCreateTime = birth_time; - nTimeBegin = std::min(nTimeBegin, birth_time); - } } + file.close(); + pwallet->ShowProgress("", 100); // hide progress dialog in GUI + pwallet->UpdateTimeFirstKey(nTimeBegin); } - file.close(); - pwallet->ShowProgress("", 100); // hide progress dialog in GUI - pwallet->UpdateTimeFirstKey(nTimeBegin); - pwallet->RescanFromTime(nTimeBegin, false /* update */); + pwallet->RescanFromTime(nTimeBegin, reserver, false /* update */); pwallet->MarkDirty(); if (!fGood) @@ -685,7 +711,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime())); file << "\n"; - // add the base58check encoded extended master if the wallet uses HD + // add the base58check encoded extended master if the wallet uses HD CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID; if (!masterKeyID.IsNull()) { @@ -1110,6 +1136,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) " {\n" " \"rescan\": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n" " }\n" + "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n" "\nExamples:\n" + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, " "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + @@ -1135,49 +1163,55 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) } } - LOCK2(cs_main, pwallet->cs_wallet); - EnsureWalletIsUnlocked(pwallet); - - // Verify all timestamps are present before importing any keys. - const int64_t now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0; - for (const UniValue& data : requests.getValues()) { - GetImportTimestamp(data, now); + WalletRescanReserver reserver(pwallet); + if (fRescan && !reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } + int64_t now = 0; bool fRunScan = false; - const int64_t minimumTimestamp = 1; int64_t nLowestTimestamp = 0; - - if (fRescan && chainActive.Tip()) { - nLowestTimestamp = chainActive.Tip()->GetBlockTime(); - } else { - fRescan = false; - } - UniValue response(UniValue::VARR); + { + LOCK2(cs_main, pwallet->cs_wallet); + EnsureWalletIsUnlocked(pwallet); - for (const UniValue& data : requests.getValues()) { - const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp); - const UniValue result = ProcessImport(pwallet, data, timestamp); - response.push_back(result); - - if (!fRescan) { - continue; + // Verify all timestamps are present before importing any keys. + now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0; + for (const UniValue& data : requests.getValues()) { + GetImportTimestamp(data, now); } - // If at least one request was successful then allow rescan. - if (result["success"].get_bool()) { - fRunScan = true; + const int64_t minimumTimestamp = 1; + + if (fRescan && chainActive.Tip()) { + nLowestTimestamp = chainActive.Tip()->GetBlockTime(); + } else { + fRescan = false; } - // Get the lowest timestamp. - if (timestamp < nLowestTimestamp) { - nLowestTimestamp = timestamp; + for (const UniValue& data : requests.getValues()) { + const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp); + const UniValue result = ProcessImport(pwallet, data, timestamp); + response.push_back(result); + + if (!fRescan) { + continue; + } + + // If at least one request was successful then allow rescan. + if (result["success"].get_bool()) { + fRunScan = true; + } + + // Get the lowest timestamp. + if (timestamp < nLowestTimestamp) { + nLowestTimestamp = timestamp; + } } } - if (fRescan && fRunScan && requests.size()) { - int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, true /* update */); + int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */); pwallet->ReacceptWalletTransactions(); if (scannedTime > nLowestTimestamp) { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e307623fd5..a77a886428 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -18,6 +18,7 @@ #include <rpc/mining.h> #include <rpc/safemode.h> #include <rpc/server.h> +#include <rpc/util.h> #include <script/sign.h> #include <timedata.h> #include <util.h> @@ -144,7 +145,7 @@ UniValue getnewaddress(const JSONRPCRequest& request) "so payments received with the address will be credited to 'account'.\n" "\nArguments:\n" "1. \"account\" (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n" - "2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -addresstype.\n" + "2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n" "\nResult:\n" "\"address\" (string) The new bitcoin address\n" "\nExamples:\n" @@ -242,7 +243,7 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) "\nReturns a new Bitcoin address, for receiving change.\n" "This is for use with raw transactions, NOT normal use.\n" "\nArguments:\n" - "1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -changetype.\n" + "1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" "\nResult:\n" "\"address\" (string) The address\n" "\nExamples:\n" @@ -1161,9 +1162,6 @@ UniValue sendmany(const JSONRPCRequest& request) return wtx.GetHash().GetHex(); } -// Defined in rpc/misc.cpp -extern CScript _createmultisig_redeemScript(CWallet * const pwallet, const UniValue& params); - UniValue addmultisigaddress(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -1181,16 +1179,22 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) "If 'account' is specified (DEPRECATED), assign address to that account.\n" "\nArguments:\n" - "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n" - "2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n" + "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n" + "2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n" " [\n" - " \"address\" (string) bitcoin address or hex-encoded public key\n" + " \"address\" (string) bitcoin address or hex-encoded public key\n" " ...,\n" " ]\n" - "3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n" + "3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n" "\nResult:\n" - "\"address\" (string) A bitcoin address associated with the keys.\n" + "{\n" + " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n" + " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n" + "}\n" + "\nResult (DEPRECATED. To see this result in v0.16 instead, please start bitcoind with -deprecatedrpc=addmultisigaddress).\n" + " clients should transition to the new output api before upgrading to v0.17.\n" + "\"address\" (string) A bitcoin address associated with the keys.\n" "\nExamples:\n" "\nAdd a multisig address from 2 addresses\n" @@ -1207,14 +1211,34 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) if (!request.params[2].isNull()) strAccount = AccountFromValue(request.params[2]); + int required = request.params[0].get_int(); + + // Get the public keys + const UniValue& keys_or_addrs = request.params[1].get_array(); + std::vector<CPubKey> pubkeys; + for (unsigned int i = 0; i < keys_or_addrs.size(); ++i) { + if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) { + pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str())); + } else { + pubkeys.push_back(AddrToPubKey(pwallet, keys_or_addrs[i].get_str())); + } + } + // Construct using pay-to-script-hash: - CScript inner = _createmultisig_redeemScript(pwallet, request.params); + CScript inner = CreateMultisigRedeemscript(required, pubkeys); pwallet->AddCScript(inner); - CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, g_address_type); - pwallet->SetAddressBook(dest, strAccount, "send"); - return EncodeDestination(dest); + + // Return old style interface + if (IsDeprecatedRPCEnabled("addmultisigaddress")) { + return EncodeDestination(dest); + } + + UniValue result(UniValue::VOBJ); + result.pushKV("address", EncodeDestination(dest)); + result.pushKV("redeemScript", HexStr(inner.begin(), inner.end())); + return result; } class Witnessifier : public boost::static_visitor<bool> @@ -1285,7 +1309,8 @@ UniValue addwitnessaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { std::string msg = "addwitnessaddress \"address\" ( p2sh )\n" - "\nAdd a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\n" + "\nDEPRECATED: set the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n" + "Add a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\n" "It returns the witness script.\n" "\nArguments:\n" @@ -1299,6 +1324,12 @@ UniValue addwitnessaddress(const JSONRPCRequest& request) throw std::runtime_error(msg); } + if (!IsDeprecatedRPCEnabled("addwitnessaddress")) { + throw JSONRPCError(RPC_METHOD_DEPRECATED, "addwitnessaddress is deprecated and will be fully removed in v0.17. " + "To use addwitnessaddress in v0.16, restart bitcoind with -deprecatedrpc=addwitnessaddress.\n" + "Projects should transition to using the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n"); + } + { LOCK(cs_main); if (!IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()) && !gArgs.GetBoolArg("-walletprematurewitness", false)) { @@ -2283,7 +2314,8 @@ UniValue walletpassphrase(const JSONRPCRequest& request) "This is needed prior to performing transactions related to private keys such as sending bitcoins\n" "\nArguments:\n" "1. \"passphrase\" (string, required) The wallet passphrase\n" - "2. timeout (numeric, required) The time to keep the decryption key in seconds.\n" + "2. timeout (numeric, required) The time to keep the decryption key in seconds. Limited to at most 1073741824 (2^30) seconds.\n" + " Any value greater than 1073741824 seconds will be set to 1073741824 seconds.\n" "\nNote:\n" "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" "time that overrides the old one.\n" @@ -2312,6 +2344,17 @@ UniValue walletpassphrase(const JSONRPCRequest& request) // Alternately, find a way to make request.params[0] mlock()'d to begin with. strWalletPass = request.params[0].get_str().c_str(); + // Get the timeout + int64_t nSleepTime = request.params[1].get_int64(); + // Timeout cannot be negative, otherwise it will relock immediately + if (nSleepTime < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative."); + } + // Clamp timeout to 2^30 seconds + if (nSleepTime > (int64_t)1 << 30) { + nSleepTime = (int64_t)1 << 30; + } + if (strWalletPass.length() > 0) { if (!pwallet->Unlock(strWalletPass)) { @@ -2325,7 +2368,6 @@ UniValue walletpassphrase(const JSONRPCRequest& request) pwallet->TopUpKeyPool(); - int64_t nSleepTime = request.params[1].get_int64(); pwallet->nRelockTime = GetTime() + nSleepTime; RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), boost::bind(LockWallet, pwallet), nSleepTime); @@ -3398,30 +3440,41 @@ UniValue rescanblockchain(const JSONRPCRequest& request) ); } - LOCK2(cs_main, pwallet->cs_wallet); + WalletRescanReserver reserver(pwallet); + if (!reserver.reserve()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); + } - CBlockIndex *pindexStart = chainActive.Genesis(); + CBlockIndex *pindexStart = nullptr; CBlockIndex *pindexStop = nullptr; - if (!request.params[0].isNull()) { - pindexStart = chainActive[request.params[0].get_int()]; - if (!pindexStart) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); - } - } + CBlockIndex *pChainTip = nullptr; + { + LOCK(cs_main); + pindexStart = chainActive.Genesis(); + pChainTip = chainActive.Tip(); - if (!request.params[1].isNull()) { - pindexStop = chainActive[request.params[1].get_int()]; - if (!pindexStop) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); + if (!request.params[0].isNull()) { + pindexStart = chainActive[request.params[0].get_int()]; + if (!pindexStart) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); + } } - else if (pindexStop->nHeight < pindexStart->nHeight) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height"); + + if (!request.params[1].isNull()) { + pindexStop = chainActive[request.params[1].get_int()]; + if (!pindexStop) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); + } + else if (pindexStop->nHeight < pindexStart->nHeight) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height"); + } } } // We can't rescan beyond non-pruned blocks, stop and throw an error if (fPruneMode) { - CBlockIndex *block = pindexStop ? pindexStop : chainActive.Tip(); + LOCK(cs_main); + CBlockIndex *block = pindexStop ? pindexStop : pChainTip; while (block && block->nHeight >= pindexStart->nHeight) { if (!(block->nStatus & BLOCK_HAVE_DATA)) { throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height."); @@ -3430,18 +3483,17 @@ UniValue rescanblockchain(const JSONRPCRequest& request) } } - CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, true); + CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, true); if (!stopBlock) { if (pwallet->IsAbortingRescan()) { throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted."); } // if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex - stopBlock = pindexStop ? pindexStop : chainActive.Tip(); + stopBlock = pindexStop ? pindexStop : pChainTip; } else { throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files."); } - UniValue response(UniValue::VOBJ); response.pushKV("start_height", pindexStart->nHeight); response.pushKV("stop_height", stopBlock->nHeight); @@ -3468,7 +3520,7 @@ static const CRPCCommand commands[] = { "wallet", "abandontransaction", &abandontransaction, {"txid"} }, { "wallet", "abortrescan", &abortrescan, {} }, { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","account"} }, - { "wallet", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} }, + { "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} }, { "wallet", "backupwallet", &backupwallet, {"destination"} }, { "wallet", "bumpfee", &bumpfee, {"txid", "options"} }, { "wallet", "dumpprivkey", &dumpprivkey, {"address"} }, diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 7b3c283f37..7e0881afd7 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -384,7 +384,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) { CWallet wallet; AddKey(wallet, coinbaseKey); - BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr)); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); } @@ -397,7 +399,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) { CWallet wallet; AddKey(wallet, coinbaseKey); - BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr)); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver)); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); } @@ -608,7 +612,9 @@ public: bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); - wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr); + WalletRescanReserver reserver(wallet.get()); + reserver.reserve(); + wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver); } ~ListCoinsTestingSetup() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index da721ea008..dd9bc64728 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1612,19 +1612,20 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, * @return Earliest timestamp that could be successfully scanned from. Timestamp * returned will be higher than startTime if relevant blocks could not be read. */ -int64_t CWallet::RescanFromTime(int64_t startTime, bool update) +int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update) { - AssertLockHeld(cs_main); - AssertLockHeld(cs_wallet); - // Find starting block. May be null if nCreateTime is greater than the // highest blockchain timestamp, in which case there is nothing that needs // to be scanned. - CBlockIndex* const startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); - LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); + CBlockIndex* startBlock = nullptr; + { + LOCK(cs_main); + startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); + LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); + } if (startBlock) { - const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, update); + const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update); if (failedBlock) { return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1; } @@ -1643,12 +1644,17 @@ int64_t CWallet::RescanFromTime(int64_t startTime, bool update) * * If pindexStop is not a nullptr, the scan will stop at the block-index * defined by pindexStop + * + * Caller needs to make sure pindexStop (and the optional pindexStart) are on + * the main chain after to the addition of any new keys you want to detect + * transactions for. */ -CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate) +CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver &reserver, bool fUpdate) { int64_t nNow = GetTime(); const CChainParams& chainParams = Params(); + assert(reserver.isReserved()); if (pindexStop) { assert(pindexStop->nHeight >= pindexStart->nHeight); } @@ -1656,24 +1662,42 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock CBlockIndex* pindex = pindexStart; CBlockIndex* ret = nullptr; { - LOCK2(cs_main, cs_wallet); fAbortRescan = false; - fScanningWallet = true; - ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup - double dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex); - double dProgressTip = GuessVerificationProgress(chainParams.TxData(), chainActive.Tip()); + CBlockIndex* tip = nullptr; + double dProgressStart; + double dProgressTip; + { + LOCK(cs_main); + tip = chainActive.Tip(); + dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex); + dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip); + } while (pindex && !fAbortRescan) { - 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)))); + if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) { + double gvp = 0; + { + LOCK(cs_main); + gvp = GuessVerificationProgress(chainParams.TxData(), pindex); + } + ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((gvp - dProgressStart) / (dProgressTip - dProgressStart) * 100)))); + } if (GetTime() >= nNow + 60) { nNow = GetTime(); + LOCK(cs_main); LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex)); } CBlock block; if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) { + LOCK2(cs_main, cs_wallet); + if (pindex && !chainActive.Contains(pindex)) { + // Abort scan if current block is no longer active, to prevent + // marking transactions as coming from the wrong block. + ret = pindex; + break; + } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate); } @@ -1683,14 +1707,20 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock if (pindex == pindexStop) { break; } - pindex = chainActive.Next(pindex); + { + LOCK(cs_main); + pindex = chainActive.Next(pindex); + if (tip != chainActive.Tip()) { + tip = chainActive.Tip(); + // in case the tip has changed, update progress max + dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip); + } + } } if (pindex && fAbortRescan) { LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex)); } ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI - - fScanningWallet = false; } return ret; } @@ -4001,7 +4031,14 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) } nStart = GetTimeMillis(); - walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, true); + { + WalletRescanReserver reserver(walletInstance); + if (!reserver.reserve()) { + InitError(_("Failed to rescan the wallet during initialization")); + return nullptr; + } + walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true); + } LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); walletInstance->SetBestChain(chainActive.GetLocator()); walletInstance->dbw->IncrementUpdateCounter(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 53a2c6b9d5..354f524821 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -99,7 +99,7 @@ enum WalletFeature FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version }; -enum OutputType +enum OutputType : int { OUTPUT_TYPE_NONE, OUTPUT_TYPE_LEGACY, @@ -659,6 +659,7 @@ private: }; +class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime /** * A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, * and provides the ability to create new transactions. @@ -668,7 +669,10 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface private: static std::atomic<bool> fFlushScheduled; std::atomic<bool> fAbortRescan; - std::atomic<bool> fScanningWallet; + std::atomic<bool> fScanningWallet; //controlled by WalletRescanReserver + std::mutex mutexScanning; + friend class WalletRescanReserver; + /** * Select a set of coins such that nValueRet >= nTargetValue and at least @@ -945,8 +949,8 @@ public: void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) override; void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override; bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate); - int64_t RescanFromTime(int64_t startTime, bool update); - CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate = false); + int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); + CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate = false); void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; void ReacceptWalletTransactions(); void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override; @@ -1263,4 +1267,39 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType); /** Get all destinations (potentially) supported by the wallet for the given key. */ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key); +/** RAII object to check and reserve a wallet rescan */ +class WalletRescanReserver +{ +private: + CWalletRef m_wallet; + bool m_could_reserve; +public: + explicit WalletRescanReserver(CWalletRef w) : m_wallet(w), m_could_reserve(false) {} + + bool reserve() + { + assert(!m_could_reserve); + std::lock_guard<std::mutex> lock(m_wallet->mutexScanning); + if (m_wallet->fScanningWallet) { + return false; + } + m_wallet->fScanningWallet = true; + m_could_reserve = true; + return true; + } + + bool isReserved() const + { + return (m_could_reserve && m_wallet->fScanningWallet); + } + + ~WalletRescanReserver() + { + std::lock_guard<std::mutex> lock(m_wallet->mutexScanning); + if (m_could_reserve) { + m_wallet->fScanningWallet = false; + } + } +}; + #endif // BITCOIN_WALLET_WALLET_H diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp index f15e5de1e2..7c97b668ae 100644 --- a/src/wallet/walletutil.cpp +++ b/src/wallet/walletutil.cpp @@ -9,7 +9,7 @@ fs::path GetWalletDir() fs::path path; if (gArgs.IsArgSet("-walletdir")) { - path = fs::system_complete(gArgs.GetArg("-walletdir", "")); + path = gArgs.GetArg("-walletdir", ""); if (!fs::is_directory(path)) { // If the path specified doesn't exist, we return the deliberately // invalid empty string. |