diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 8d25db5f6..ca4eaa173 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -1,405 +1,405 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "transactiondesc.h" #include "bitcoinunits.h" #include "guiutil.h" #include "paymentserver.h" #include "transactionrecord.h" #include "chain.h" #include "consensus/consensus.h" #include "dstencode.h" #include "script/script.h" #include "timedata.h" #include "util.h" #include "validation.h" #include "wallet/db.h" #include "wallet/finaltx.h" #include "wallet/wallet.h" #include #include QString TransactionDesc::FormatTxStatus(const CWalletTx &wtx) { AssertLockHeld(cs_main); - if (!CheckFinalTx(wtx)) { + if (!CheckFinalTx(*wtx.tx)) { if (wtx.tx->nLockTime < LOCKTIME_THRESHOLD) { return tr("Open for %n more block(s)", "", wtx.tx->nLockTime - chainActive.Height()); } else { return tr("Open until %1") .arg(GUIUtil::dateTimeStr(wtx.tx->nLockTime)); } } else { int nDepth = wtx.GetDepthInMainChain(); if (nDepth < 0) { return tr("conflicted with a transaction with %1 confirmations") .arg(-nDepth); } else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) { return tr("%1/offline").arg(nDepth); } else if (nDepth == 0) { return tr("0/unconfirmed, %1") .arg((wtx.InMempool() ? tr("in memory pool") : tr("not in memory pool"))) + (wtx.isAbandoned() ? ", " + tr("abandoned") : ""); } else if (nDepth < 6) { return tr("%1/unconfirmed").arg(nDepth); } else { return tr("%1 confirmations").arg(nDepth); } } } QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionRecord *rec, int unit) { QString strHTML; LOCK2(cs_main, wallet->cs_wallet); strHTML.reserve(4000); strHTML += ""; int64_t nTime = wtx.GetTxTime(); Amount nCredit = wtx.GetCredit(ISMINE_ALL); Amount nDebit = wtx.GetDebit(ISMINE_ALL); Amount nNet = nCredit - nDebit; strHTML += "" + tr("Status") + ": " + FormatTxStatus(wtx); int nRequests = wtx.GetRequestCount(); if (nRequests != -1) { if (nRequests == 0) { strHTML += tr(", has not been successfully broadcast yet"); } else if (nRequests > 0) { strHTML += tr(", broadcast through %n node(s)", "", nRequests); } } strHTML += "
"; strHTML += "" + tr("Date") + ": " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "
"; // // From // if (wtx.IsCoinBase()) { strHTML += "" + tr("Source") + ": " + tr("Generated") + "
"; } else if (wtx.mapValue.count("from") && !wtx.mapValue["from"].empty()) { // Online transaction strHTML += "" + tr("From") + ": " + GUIUtil::HtmlEscape(wtx.mapValue["from"]) + "
"; } else { // Offline transaction if (nNet > Amount::zero()) { // Credit CTxDestination address = DecodeDestination(rec->address, wallet->chainParams); if (IsValidDestination(address)) { if (wallet->mapAddressBook.count(address)) { strHTML += "" + tr("From") + ": " + tr("unknown") + "
"; strHTML += "" + tr("To") + ": "; strHTML += GUIUtil::HtmlEscape(rec->address); QString addressOwned = (::IsMine(*wallet, address) == ISMINE_SPENDABLE) ? tr("own address") : tr("watch-only"); if (!wallet->mapAddressBook[address].name.empty()) { strHTML += " (" + addressOwned + ", " + tr("label") + ": " + GUIUtil::HtmlEscape( wallet->mapAddressBook[address].name) + ")"; } else { strHTML += " (" + addressOwned + ")"; } strHTML += "
"; } } } } // // To // if (wtx.mapValue.count("to") && !wtx.mapValue["to"].empty()) { // Online transaction std::string strAddress = wtx.mapValue["to"]; strHTML += "" + tr("To") + ": "; CTxDestination dest = DecodeDestination(strAddress, wallet->chainParams); if (wallet->mapAddressBook.count(dest) && !wallet->mapAddressBook[dest].name.empty()) { strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[dest].name) + " "; } strHTML += GUIUtil::HtmlEscape(strAddress) + "
"; } // // Amount // if (wtx.IsCoinBase() && nCredit == Amount::zero()) { // // Coinbase // Amount nUnmatured = Amount::zero(); for (const CTxOut &txout : wtx.tx->vout) { nUnmatured += wallet->GetCredit(txout, ISMINE_ALL); } strHTML += "" + tr("Credit") + ": "; if (wtx.IsInMainChain()) { strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured) + " (" + tr("matures in %n more block(s)", "", wtx.GetBlocksToMaturity()) + ")"; } else { strHTML += "(" + tr("not accepted") + ")"; } strHTML += "
"; } else if (nNet > Amount::zero()) { // // Credit // strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "
"; } else { isminetype fAllFromMe = ISMINE_SPENDABLE; for (const CTxIn &txin : wtx.tx->vin) { isminetype mine = wallet->IsMine(txin); if (fAllFromMe > mine) { fAllFromMe = mine; } } isminetype fAllToMe = ISMINE_SPENDABLE; for (const CTxOut &txout : wtx.tx->vout) { isminetype mine = wallet->IsMine(txout); if (fAllToMe > mine) { fAllToMe = mine; } } if (fAllFromMe) { if (fAllFromMe & ISMINE_WATCH_ONLY) { strHTML += "" + tr("From") + ": " + tr("watch-only") + "
"; } // // Debit // for (const CTxOut &txout : wtx.tx->vout) { // Ignore change isminetype toSelf = wallet->IsMine(txout); if ((toSelf == ISMINE_SPENDABLE) && (fAllFromMe == ISMINE_SPENDABLE)) { continue; } if (!wtx.mapValue.count("to") || wtx.mapValue["to"].empty()) { // Offline transaction CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address)) { strHTML += "" + tr("To") + ": "; if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty()) { strHTML += GUIUtil::HtmlEscape( wallet->mapAddressBook[address].name) + " "; } strHTML += GUIUtil::HtmlEscape(EncodeDestination(address)); if (toSelf == ISMINE_SPENDABLE) { strHTML += " (own address)"; } else if (toSelf & ISMINE_WATCH_ONLY) { strHTML += " (watch-only)"; } strHTML += "
"; } } strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -1 * txout.nValue) + "
"; if (toSelf) { strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "
"; } } if (fAllToMe) { // Payment to self Amount nChange = wtx.GetChange(); Amount nValue = nCredit - nChange; strHTML += "" + tr("Total debit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -1 * nValue) + "
"; strHTML += "" + tr("Total credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "
"; } Amount nTxFee = nDebit - wtx.tx->GetValueOut(); if (nTxFee > Amount::zero()) strHTML += "" + tr("Transaction fee") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -1 * nTxFee) + "
"; } else { // // Mixed debit transaction // for (const CTxIn &txin : wtx.tx->vin) { if (wallet->IsMine(txin)) { strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, -1 * wallet->GetDebit(txin, ISMINE_ALL)) + "
"; } } for (const CTxOut &txout : wtx.tx->vout) { if (wallet->IsMine(txout)) { strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, wallet->GetCredit(txout, ISMINE_ALL)) + "
"; } } } } strHTML += "" + tr("Net amount") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "
"; // // Message // if (wtx.mapValue.count("message") && !wtx.mapValue["message"].empty()) { strHTML += "
" + tr("Message") + ":
" + GUIUtil::HtmlEscape(wtx.mapValue["message"], true) + "
"; } if (wtx.mapValue.count("comment") && !wtx.mapValue["comment"].empty()) { strHTML += "
" + tr("Comment") + ":
" + GUIUtil::HtmlEscape(wtx.mapValue["comment"], true) + "
"; } strHTML += "" + tr("Transaction ID") + ": " + rec->getTxID() + "
"; strHTML += "" + tr("Transaction total size") + ": " + QString::number(wtx.tx->GetTotalSize()) + " bytes
"; strHTML += "" + tr("Output index") + ": " + QString::number(rec->getOutputIndex()) + "
"; // Message from normal bitcoincash:URI (bitcoincash:123...?message=example) for (const std::pair &r : wtx.vOrderForm) { if (r.first == "Message") { strHTML += "
" + tr("Message") + ":
" + GUIUtil::HtmlEscape(r.second, true) + "
"; } } // // PaymentRequest info: // for (const std::pair &r : wtx.vOrderForm) { if (r.first == "PaymentRequest") { PaymentRequestPlus req; req.parse( QByteArray::fromRawData(r.second.data(), r.second.size())); QString merchant; if (req.getMerchant(PaymentServer::getCertStore(), merchant)) { strHTML += "" + tr("Merchant") + ": " + GUIUtil::HtmlEscape(merchant) + "
"; } } } if (wtx.IsCoinBase()) { quint32 numBlocksToMaturity = COINBASE_MATURITY + 1; strHTML += "
" + tr("Generated coins must mature %1 blocks before they can be " "spent. When you generated this block, it was broadcast to the " "network to be added to the block chain. If it fails to get " "into the chain, its state will change to \"not accepted\" and " "it won't be spendable. This may occasionally happen if another " "node generates a block within a few seconds of yours.") .arg(QString::number(numBlocksToMaturity)) + "
"; } // // Debug view // if (gArgs.GetBoolArg("-debug", false)) { strHTML += "

" + tr("Debug information") + "

"; for (const CTxIn &txin : wtx.tx->vin) { if (wallet->IsMine(txin)) { strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, -1 * wallet->GetDebit(txin, ISMINE_ALL)) + "
"; } } for (const CTxOut &txout : wtx.tx->vout) { if (wallet->IsMine(txout)) { strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit( unit, wallet->GetCredit(txout, ISMINE_ALL)) + "
"; } } strHTML += "
" + tr("Transaction") + ":
"; strHTML += GUIUtil::HtmlEscape(wtx.tx->ToString(), true); strHTML += "
" + tr("Inputs") + ":"; strHTML += "
    "; for (const CTxIn &txin : wtx.tx->vin) { COutPoint prevout = txin.prevout; Coin prev; if (pcoinsTip->GetCoin(prevout, prev)) { strHTML += "
  • "; const CTxOut &vout = prev.GetTxOut(); CTxDestination address; if (ExtractDestination(vout.scriptPubKey, address)) { if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty()) { strHTML += GUIUtil::HtmlEscape( wallet->mapAddressBook[address].name) + " "; } strHTML += QString::fromStdString(EncodeDestination(address)); } strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue); strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "
  • "; strHTML = strHTML + " IsWatchOnly=" + (wallet->IsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + ""; } } strHTML += "
"; } strHTML += "
"; return strHTML; } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index e14822255..688e2a6e0 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -1,236 +1,236 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "transactionrecord.h" #include "chain.h" #include "consensus/consensus.h" #include "dstencode.h" #include "timedata.h" #include "validation.h" #include "wallet/finaltx.h" #include "wallet/wallet.h" #include /** * Return positive answer if transaction should be shown in list. */ bool TransactionRecord::showTransaction(const CWalletTx &wtx) { // There are currently no cases where we hide transactions, but we may want // to use this in the future for things like RBF. return true; } /** * Decompose CWallet transaction to model transaction records. */ QList TransactionRecord::decomposeTransaction(const CWallet *wallet, const CWalletTx &wtx) { QList parts; int64_t nTime = wtx.GetTxTime(); Amount nCredit = wtx.GetCredit(ISMINE_ALL); Amount nDebit = wtx.GetDebit(ISMINE_ALL); Amount nNet = nCredit - nDebit; const TxId &txid = wtx.GetId(); std::map mapValue = wtx.mapValue; if (nNet > Amount::zero() || wtx.IsCoinBase()) { // // Credit // for (size_t i = 0; i < wtx.tx->vout.size(); i++) { const CTxOut &txout = wtx.tx->vout[i]; isminetype mine = wallet->IsMine(txout); if (mine) { TransactionRecord sub(txid, nTime); CTxDestination address; sub.idx = i; // vout index sub.credit = txout.nValue; sub.involvesWatchAddress = mine & ISMINE_WATCH_ONLY; if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address)) { // Received by Bitcoin Address sub.type = TransactionRecord::RecvWithAddress; sub.address = EncodeDestination(address); } else { // Received by IP connection (deprecated features), or a // multisignature or other non-simple transaction sub.type = TransactionRecord::RecvFromOther; sub.address = mapValue["from"]; } if (wtx.IsCoinBase()) { // Generated sub.type = TransactionRecord::Generated; } parts.append(sub); } } } else { bool involvesWatchAddress = false; isminetype fAllFromMe = ISMINE_SPENDABLE; for (const CTxIn &txin : wtx.tx->vin) { isminetype mine = wallet->IsMine(txin); if (mine & ISMINE_WATCH_ONLY) { involvesWatchAddress = true; } if (fAllFromMe > mine) { fAllFromMe = mine; } } isminetype fAllToMe = ISMINE_SPENDABLE; for (const CTxOut &txout : wtx.tx->vout) { isminetype mine = wallet->IsMine(txout); if (mine & ISMINE_WATCH_ONLY) { involvesWatchAddress = true; } if (fAllToMe > mine) { fAllToMe = mine; } } if (fAllFromMe && fAllToMe) { // Payment to self Amount nChange = wtx.GetChange(); parts.append(TransactionRecord( txid, nTime, TransactionRecord::SendToSelf, "", -1 * (nDebit - nChange), (nCredit - nChange))); // maybe pass to TransactionRecord as constructor argument parts.last().involvesWatchAddress = involvesWatchAddress; } else if (fAllFromMe) { // // Debit // Amount nTxFee = nDebit - wtx.tx->GetValueOut(); for (size_t nOut = 0; nOut < wtx.tx->vout.size(); nOut++) { const CTxOut &txout = wtx.tx->vout[nOut]; TransactionRecord sub(txid, nTime); sub.idx = nOut; sub.involvesWatchAddress = involvesWatchAddress; if (wallet->IsMine(txout)) { // Ignore parts sent to self, as this is usually the change // from a transaction sent back to our own address. continue; } CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address)) { // Sent to Bitcoin Address sub.type = TransactionRecord::SendToAddress; sub.address = EncodeDestination(address); } else { // Sent to IP, or other non-address transaction like OP_EVAL sub.type = TransactionRecord::SendToOther; sub.address = mapValue["to"]; } Amount nValue = txout.nValue; /* Add fee to first output */ if (nTxFee > Amount::zero()) { nValue += nTxFee; nTxFee = Amount::zero(); } sub.debit = -1 * nValue; parts.append(sub); } } else { // // Mixed debit transaction, can't break down payees // parts.append(TransactionRecord(txid, nTime, TransactionRecord::Other, "", nNet, Amount::zero())); parts.last().involvesWatchAddress = involvesWatchAddress; } } return parts; } void TransactionRecord::updateStatus(const CWalletTx &wtx) { AssertLockHeld(cs_main); // Determine transaction status // Find the block the tx is in CBlockIndex *pindex = nullptr; BlockMap::iterator mi = mapBlockIndex.find(wtx.hashBlock); if (mi != mapBlockIndex.end()) { pindex = (*mi).second; } // Sort order, unrecorded transactions sort to the top status.sortKey = strprintf("%010d-%01d-%010u-%03d", (pindex ? pindex->nHeight : std::numeric_limits::max()), (wtx.IsCoinBase() ? 1 : 0), wtx.nTimeReceived, idx); status.countsForBalance = wtx.IsTrusted() && !(wtx.GetBlocksToMaturity() > 0); status.depth = wtx.GetDepthInMainChain(); status.cur_num_blocks = chainActive.Height(); - if (!CheckFinalTx(wtx)) { + if (!CheckFinalTx(*wtx.tx)) { if (wtx.tx->nLockTime < LOCKTIME_THRESHOLD) { status.status = TransactionStatus::OpenUntilBlock; status.open_for = wtx.tx->nLockTime - chainActive.Height(); } else { status.status = TransactionStatus::OpenUntilDate; status.open_for = wtx.tx->nLockTime; } } else if (type == TransactionRecord::Generated) { // For generated transactions, determine maturity if (wtx.GetBlocksToMaturity() > 0) { status.status = TransactionStatus::Immature; if (wtx.IsInMainChain()) { status.matures_in = wtx.GetBlocksToMaturity(); // Check if the block was requested by anyone if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) { status.status = TransactionStatus::MaturesWarning; } } else { status.status = TransactionStatus::NotAccepted; } } else { status.status = TransactionStatus::Confirmed; } } else { if (status.depth < 0) { status.status = TransactionStatus::Conflicted; } else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0) { status.status = TransactionStatus::Offline; } else if (status.depth == 0) { status.status = TransactionStatus::Unconfirmed; if (wtx.isAbandoned()) { status.status = TransactionStatus::Abandoned; } } else if (status.depth < RecommendedNumConfirmations) { status.status = TransactionStatus::Confirming; } else { status.status = TransactionStatus::Confirmed; } } } bool TransactionRecord::statusUpdateNeeded() const { AssertLockHeld(cs_main); return status.cur_num_blocks != chainActive.Height(); } QString TransactionRecord::getTxID() const { return QString::fromStdString(txid.ToString()); } int TransactionRecord::getOutputIndex() const { return idx; } diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index bfc88c16c..87405d772 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -1,800 +1,799 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "transactiontablemodel.h" #include "addresstablemodel.h" #include "guiconstants.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" #include "transactiondesc.h" #include "transactionrecord.h" #include "walletmodel.h" #include "core_io.h" #include "sync.h" #include "uint256.h" #include "util.h" #include "validation.h" #include "wallet/wallet.h" #include #include #include #include #include // Amount column is right-aligned it contains numbers static int column_alignments[] = { Qt::AlignLeft | Qt::AlignVCenter, /* status */ Qt::AlignLeft | Qt::AlignVCenter, /* watchonly */ Qt::AlignLeft | Qt::AlignVCenter, /* date */ Qt::AlignLeft | Qt::AlignVCenter, /* type */ Qt::AlignLeft | Qt::AlignVCenter, /* address */ Qt::AlignRight | Qt::AlignVCenter /* amount */ }; // Comparison operator for sort/binary search of model tx list struct TxLessThan { bool operator()(const TransactionRecord &a, const TransactionRecord &b) const { return a.txid < b.txid; } bool operator()(const TransactionRecord &a, const TxId &b) const { return a.txid < b; } bool operator()(const TxId &a, const TransactionRecord &b) const { return a < b.txid; } }; // Private implementation class TransactionTablePriv { public: TransactionTablePriv(CWallet *_wallet, TransactionTableModel *_parent) : wallet(_wallet), parent(_parent) {} CWallet *wallet; TransactionTableModel *parent; /* Local cache of wallet. * As it is in the same order as the CWallet, by definition this is sorted * by sha256. */ QList cachedWallet; /** * Query entire wallet anew from core. */ void refreshWallet() { qDebug() << "TransactionTablePriv::refreshWallet"; cachedWallet.clear(); LOCK2(cs_main, wallet->cs_wallet); for (const auto &entry : wallet->mapWallet) { if (TransactionRecord::showTransaction(entry.second)) { cachedWallet.append(TransactionRecord::decomposeTransaction( wallet, entry.second)); } } } /** * Update our model of the wallet incrementally, to synchronize our model of * the wallet with that of the core. * Call with transaction that was added, removed or changed. */ void updateWallet(const TxId &txid, int status, bool showTransaction) { qDebug() << "TransactionTablePriv::updateWallet: " + QString::fromStdString(txid.ToString()) + " " + QString::number(status); // Find bounds of this transaction in model QList::iterator lower = qLowerBound( cachedWallet.begin(), cachedWallet.end(), txid, TxLessThan()); QList::iterator upper = qUpperBound( cachedWallet.begin(), cachedWallet.end(), txid, TxLessThan()); int lowerIndex = (lower - cachedWallet.begin()); int upperIndex = (upper - cachedWallet.begin()); bool inModel = (lower != upper); if (status == CT_UPDATED) { // Not in model, but want to show, treat as new. if (showTransaction && !inModel) { status = CT_NEW; } // In model, but want to hide, treat as deleted. if (!showTransaction && inModel) { status = CT_DELETED; } } qDebug() << " inModel=" + QString::number(inModel) + " Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) + " showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status); switch (status) { case CT_NEW: if (inModel) { qWarning() << "TransactionTablePriv::updateWallet: " "Warning: Got CT_NEW, but transaction is " "already in model"; break; } if (showTransaction) { LOCK2(cs_main, wallet->cs_wallet); // Find transaction in wallet std::map::iterator mi = wallet->mapWallet.find(txid); if (mi == wallet->mapWallet.end()) { qWarning() << "TransactionTablePriv::updateWallet: " "Warning: Got CT_NEW, but transaction is " "not in wallet"; break; } // Added -- insert at the right position QList toInsert = TransactionRecord::decomposeTransaction(wallet, mi->second); /* only if something to insert */ if (!toInsert.isEmpty()) { parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex + toInsert.size() - 1); int insert_idx = lowerIndex; for (const TransactionRecord &rec : toInsert) { cachedWallet.insert(insert_idx, rec); insert_idx += 1; } parent->endInsertRows(); } } break; case CT_DELETED: if (!inModel) { qWarning() << "TransactionTablePriv::updateWallet: " "Warning: Got CT_DELETED, but transaction is " "not in model"; break; } // Removed -- remove entire transaction from table parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex - 1); cachedWallet.erase(lower, upper); parent->endRemoveRows(); break; case CT_UPDATED: // Miscellaneous updates -- nothing to do, status update will // take care of this, and is only computed for visible // transactions. break; } } int size() { return cachedWallet.size(); } TransactionRecord *index(int idx) { if (idx >= 0 && idx < cachedWallet.size()) { TransactionRecord *rec = &cachedWallet[idx]; // Get required locks upfront. This avoids the GUI from getting // stuck if the core is holding the locks for a longer time - for // example, during a wallet rescan. // // If a status update is needed (blocks came in since last check), // update the status of this transaction from the wallet. Otherwise, // simply re-use the cached status. TRY_LOCK(cs_main, lockMain); if (lockMain) { TRY_LOCK(wallet->cs_wallet, lockWallet); if (lockWallet && rec->statusUpdateNeeded()) { std::map::iterator mi = wallet->mapWallet.find(rec->txid); if (mi != wallet->mapWallet.end()) { rec->updateStatus(mi->second); } } } return rec; } return 0; } QString describe(TransactionRecord *rec, int unit) { LOCK2(cs_main, wallet->cs_wallet); std::map::iterator mi = wallet->mapWallet.find(rec->txid); if (mi != wallet->mapWallet.end()) { return TransactionDesc::toHTML(wallet, mi->second, rec, unit); } return QString(); } QString getTxHex(TransactionRecord *rec) { LOCK2(cs_main, wallet->cs_wallet); std::map::iterator mi = wallet->mapWallet.find(rec->txid); if (mi != wallet->mapWallet.end()) { - std::string strHex = - EncodeHexTx(static_cast(mi->second)); + std::string strHex = EncodeHexTx(*mi->second.tx); return QString::fromStdString(strHex); } return QString(); } }; TransactionTableModel::TransactionTableModel( const PlatformStyle *_platformStyle, CWallet *_wallet, WalletModel *parent) : QAbstractTableModel(parent), wallet(_wallet), walletModel(parent), priv(new TransactionTablePriv(_wallet, this)), fProcessingQueuedTransactions(false), platformStyle(_platformStyle) { columns << QString() << QString() << tr("Date") << tr("Type") << tr("Label") << BitcoinUnits::getAmountColumnTitle( walletModel->getOptionsModel()->getDisplayUnit()); priv->refreshWallet(); connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); subscribeToCoreSignals(); } TransactionTableModel::~TransactionTableModel() { unsubscribeFromCoreSignals(); delete priv; } /** Updates the column title to "Amount (DisplayUnit)" and emits * headerDataChanged() signal for table headers to react. */ void TransactionTableModel::updateAmountColumnTitle() { columns[Amount] = BitcoinUnits::getAmountColumnTitle( walletModel->getOptionsModel()->getDisplayUnit()); Q_EMIT headerDataChanged(Qt::Horizontal, Amount, Amount); } void TransactionTableModel::updateTransaction(const QString &hash, int status, bool showTransaction) { TxId updated; updated.SetHex(hash.toStdString()); priv->updateWallet(updated, status, showTransaction); } void TransactionTableModel::updateConfirmations() { // Blocks came in since last poll. // Invalidate status (number of confirmations) and (possibly) description // for all rows. Qt is smart enough to only actually request the data for // the visible rows. Q_EMIT dataChanged(index(0, Status), index(priv->size() - 1, Status)); Q_EMIT dataChanged(index(0, ToAddress), index(priv->size() - 1, ToAddress)); } int TransactionTableModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return priv->size(); } int TransactionTableModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return columns.length(); } QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const { QString status; switch (wtx->status.status) { case TransactionStatus::OpenUntilBlock: status = tr("Open for %n more block(s)", "", wtx->status.open_for); break; case TransactionStatus::OpenUntilDate: status = tr("Open until %1") .arg(GUIUtil::dateTimeStr(wtx->status.open_for)); break; case TransactionStatus::Offline: status = tr("Offline"); break; case TransactionStatus::Unconfirmed: status = tr("Unconfirmed"); break; case TransactionStatus::Abandoned: status = tr("Abandoned"); break; case TransactionStatus::Confirming: status = tr("Confirming (%1 of %2 recommended confirmations)") .arg(wtx->status.depth) .arg(TransactionRecord::RecommendedNumConfirmations); break; case TransactionStatus::Confirmed: status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth); break; case TransactionStatus::Conflicted: status = tr("Conflicted"); break; case TransactionStatus::Immature: status = tr("Immature (%1 confirmations, will be available after %2)") .arg(wtx->status.depth) .arg(wtx->status.depth + wtx->status.matures_in); break; case TransactionStatus::MaturesWarning: status = tr("This block was not received by any other nodes and " "will probably not be accepted!"); break; case TransactionStatus::NotAccepted: status = tr("Generated but not accepted"); break; } return status; } QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const { if (wtx->time) { return GUIUtil::dateTimeStr(wtx->time); } return QString(); } /** * Look up address in address book, if found return label (address) otherwise * just return (address) */ QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const { QString label = walletModel->getAddressTableModel()->labelForAddress( QString::fromStdString(address)); QString description; if (!label.isEmpty()) { description += label; } if (label.isEmpty() || tooltip) { description += QString(" (") + QString::fromStdString(address) + QString(")"); } return description; } QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const { switch (wtx->type) { case TransactionRecord::RecvWithAddress: return tr("Received with"); case TransactionRecord::RecvFromOther: return tr("Received from"); case TransactionRecord::SendToAddress: case TransactionRecord::SendToOther: return tr("Sent to"); case TransactionRecord::SendToSelf: return tr("Payment to yourself"); case TransactionRecord::Generated: return tr("Mined"); default: return QString(); } } QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const { switch (wtx->type) { case TransactionRecord::Generated: return QIcon(":/icons/tx_mined"); case TransactionRecord::RecvWithAddress: case TransactionRecord::RecvFromOther: return QIcon(":/icons/tx_input"); case TransactionRecord::SendToAddress: case TransactionRecord::SendToOther: return QIcon(":/icons/tx_output"); default: return QIcon(":/icons/tx_inout"); } } QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const { QString watchAddress; if (tooltip) { // Mark transactions involving watch-only addresses by adding " // (watch-only)" watchAddress = wtx->involvesWatchAddress ? QString(" (") + tr("watch-only") + QString(")") : ""; } switch (wtx->type) { case TransactionRecord::RecvFromOther: return QString::fromStdString(wtx->address) + watchAddress; case TransactionRecord::RecvWithAddress: case TransactionRecord::SendToAddress: case TransactionRecord::Generated: return lookupAddress(wtx->address, tooltip) + watchAddress; case TransactionRecord::SendToOther: return QString::fromStdString(wtx->address) + watchAddress; case TransactionRecord::SendToSelf: default: return tr("(n/a)") + watchAddress; } } QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const { // Show addresses without label in a less visible color switch (wtx->type) { case TransactionRecord::RecvWithAddress: case TransactionRecord::SendToAddress: case TransactionRecord::Generated: { QString label = walletModel->getAddressTableModel()->labelForAddress( QString::fromStdString(wtx->address)); if (label.isEmpty()) return COLOR_BAREADDRESS; } break; case TransactionRecord::SendToSelf: return COLOR_BAREADDRESS; default: break; } return QVariant(); } QString TransactionTableModel::formatTxAmount( const TransactionRecord *wtx, bool showUnconfirmed, BitcoinUnits::SeparatorStyle separators) const { QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit, false, separators); if (showUnconfirmed) { if (!wtx->status.countsForBalance) { str = QString("[") + str + QString("]"); } } return QString(str); } QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const { switch (wtx->status.status) { case TransactionStatus::OpenUntilBlock: case TransactionStatus::OpenUntilDate: return COLOR_TX_STATUS_OPENUNTILDATE; case TransactionStatus::Offline: return COLOR_TX_STATUS_OFFLINE; case TransactionStatus::Unconfirmed: return QIcon(":/icons/transaction_0"); case TransactionStatus::Abandoned: return QIcon(":/icons/transaction_abandoned"); case TransactionStatus::Confirming: switch (wtx->status.depth) { case 1: return QIcon(":/icons/transaction_1"); case 2: return QIcon(":/icons/transaction_2"); case 3: return QIcon(":/icons/transaction_3"); case 4: return QIcon(":/icons/transaction_4"); default: return QIcon(":/icons/transaction_5"); }; case TransactionStatus::Confirmed: return QIcon(":/icons/transaction_confirmed"); case TransactionStatus::Conflicted: return QIcon(":/icons/transaction_conflicted"); case TransactionStatus::Immature: { int total = wtx->status.depth + wtx->status.matures_in; int part = (wtx->status.depth * 4 / total) + 1; return QIcon(QString(":/icons/transaction_%1").arg(part)); } case TransactionStatus::MaturesWarning: case TransactionStatus::NotAccepted: return QIcon(":/icons/transaction_0"); default: return COLOR_BLACK; } } QVariant TransactionTableModel::txWatchonlyDecoration( const TransactionRecord *wtx) const { if (wtx->involvesWatchAddress) { return QIcon(":/icons/eye"); } return QVariant(); } QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const { QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec); if (rec->type == TransactionRecord::RecvFromOther || rec->type == TransactionRecord::SendToOther || rec->type == TransactionRecord::SendToAddress || rec->type == TransactionRecord::RecvWithAddress) { tooltip += QString(" ") + formatTxToAddress(rec, true); } return tooltip; } QVariant TransactionTableModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } TransactionRecord *rec = static_cast(index.internalPointer()); switch (role) { case RawDecorationRole: switch (index.column()) { case Status: return txStatusDecoration(rec); case Watchonly: return txWatchonlyDecoration(rec); case ToAddress: return txAddressDecoration(rec); } break; case Qt::DecorationRole: { QIcon icon = qvariant_cast(index.data(RawDecorationRole)); return platformStyle->TextColorIcon(icon); } case Qt::DisplayRole: switch (index.column()) { case Date: return formatTxDate(rec); case Type: return formatTxType(rec); case ToAddress: return formatTxToAddress(rec, false); case Amount: return formatTxAmount(rec, true, BitcoinUnits::separatorAlways); } break; case Qt::EditRole: // Edit role is used for sorting, so return the unformatted values switch (index.column()) { case Status: return QString::fromStdString(rec->status.sortKey); case Date: return rec->time; case Type: return formatTxType(rec); case Watchonly: return (rec->involvesWatchAddress ? 1 : 0); case ToAddress: return formatTxToAddress(rec, true); case Amount: return qint64((rec->credit + rec->debit) / SATOSHI); } break; case Qt::ToolTipRole: return formatTooltip(rec); case Qt::TextAlignmentRole: return column_alignments[index.column()]; case Qt::ForegroundRole: // Use the "danger" color for abandoned transactions if (rec->status.status == TransactionStatus::Abandoned) { return COLOR_TX_STATUS_DANGER; } // Non-confirmed (but not immature) as transactions are grey if (!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature) { return COLOR_UNCONFIRMED; } if (index.column() == Amount && (rec->credit + rec->debit) < ::Amount::zero()) { return COLOR_NEGATIVE; } if (index.column() == ToAddress) { return addressColor(rec); } break; case TypeRole: return rec->type; case DateRole: return QDateTime::fromTime_t(static_cast(rec->time)); case WatchonlyRole: return rec->involvesWatchAddress; case WatchonlyDecorationRole: return txWatchonlyDecoration(rec); case LongDescriptionRole: return priv->describe( rec, walletModel->getOptionsModel()->getDisplayUnit()); case AddressRole: return QString::fromStdString(rec->address); case LabelRole: return walletModel->getAddressTableModel()->labelForAddress( QString::fromStdString(rec->address)); case AmountRole: return qint64((rec->credit + rec->debit) / SATOSHI); case TxIDRole: return rec->getTxID(); case TxHashRole: return QString::fromStdString(rec->txid.ToString()); case TxHexRole: return priv->getTxHex(rec); case TxPlainTextRole: { QString details; QDateTime date = QDateTime::fromTime_t(static_cast(rec->time)); QString txLabel = walletModel->getAddressTableModel()->labelForAddress( QString::fromStdString(rec->address)); details.append(date.toString("M/d/yy HH:mm")); details.append(" "); details.append(formatTxStatus(rec)); details.append(". "); if (!formatTxType(rec).isEmpty()) { details.append(formatTxType(rec)); details.append(" "); } if (!rec->address.empty()) { if (txLabel.isEmpty()) { details.append(tr("(no label)") + " "); } else { details.append("("); details.append(txLabel); details.append(") "); } details.append(QString::fromStdString(rec->address)); details.append(" "); } details.append( formatTxAmount(rec, false, BitcoinUnits::separatorNever)); return details; } case ConfirmedRole: return rec->status.countsForBalance; case FormattedAmountRole: // Used for copy/export, so don't include separators return formatTxAmount(rec, false, BitcoinUnits::separatorNever); case StatusRole: return rec->status.status; } return QVariant(); } QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { return columns[section]; } else if (role == Qt::TextAlignmentRole) { return column_alignments[section]; } else if (role == Qt::ToolTipRole) { switch (section) { case Status: return tr("Transaction status. Hover over this field to " "show number of confirmations."); case Date: return tr( "Date and time that the transaction was received."); case Type: return tr("Type of transaction."); case Watchonly: return tr("Whether or not a watch-only address is involved " "in this transaction."); case ToAddress: return tr( "User-defined intent/purpose of the transaction."); case Amount: return tr("Amount removed from or added to balance."); } } } return QVariant(); } QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const { Q_UNUSED(parent); TransactionRecord *data = priv->index(row); if (data) { return createIndex(row, column, priv->index(row)); } return QModelIndex(); } void TransactionTableModel::updateDisplayUnit() { // emit dataChanged to update Amount column with the current unit updateAmountColumnTitle(); Q_EMIT dataChanged(index(0, Amount), index(priv->size() - 1, Amount)); } // queue notifications to show a non freezing progress dialog e.g. for rescan struct TransactionNotification { public: TransactionNotification() {} TransactionNotification(TxId _txid, ChangeType _status, bool _showTransaction) : txid(_txid), status(_status), showTransaction(_showTransaction) {} void invoke(QObject *ttm) { QString strHash = QString::fromStdString(txid.GetHex()); qDebug() << "NotifyTransactionChanged: " + strHash + " status= " + QString::number(status); QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection, Q_ARG(QString, strHash), Q_ARG(int, status), Q_ARG(bool, showTransaction)); } private: TxId txid; ChangeType status; bool showTransaction; }; static bool fQueueNotifications = false; static std::vector vQueueNotifications; static void NotifyTransactionChanged(TransactionTableModel *ttm, CWallet *wallet, const TxId &txid, ChangeType status) { // Find transaction in wallet std::map::iterator mi = wallet->mapWallet.find(txid); // Determine whether to show transaction or not (determine this here so that // no relocking is needed in GUI thread) bool inWallet = mi != wallet->mapWallet.end(); bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second)); TransactionNotification notification(txid, status, showTransaction); if (fQueueNotifications) { vQueueNotifications.push_back(notification); return; } notification.invoke(ttm); } static void ShowProgress(TransactionTableModel *ttm, const std::string &title, int nProgress) { if (nProgress == 0) { fQueueNotifications = true; } if (nProgress == 100) { fQueueNotifications = false; if (vQueueNotifications.size() > 10) { // prevent balloon spam, show maximum 10 balloons QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true)); } for (size_t i = 0; i < vQueueNotifications.size(); ++i) { if (vQueueNotifications.size() - i <= 10) { QMetaObject::invokeMethod( ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false)); } vQueueNotifications[i].invoke(ttm); } // clear std::vector().swap(vQueueNotifications); } } void TransactionTableModel::subscribeToCoreSignals() { // Connect signals to wallet wallet->NotifyTransactionChanged.connect( boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); } void TransactionTableModel::unsubscribeFromCoreSignals() { // Disconnect signals from wallet wallet->NotifyTransactionChanged.disconnect( boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index ff4679d5e..8bd270b04 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1,1432 +1,1432 @@ // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "base58.h" #include "chain.h" #include "config.h" #include "core_io.h" #include "dstencode.h" #include "init.h" #include "merkleblock.h" #include "rpc/safemode.h" #include "rpc/server.h" #include "rpcwallet.h" #include "script/script.h" #include "script/standard.h" #include "sync.h" #include "util.h" #include "utiltime.h" #include "validation.h" #include "wallet.h" #include #include #include #include #include #include static int64_t DecodeDumpTime(const std::string &str) { static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); static const std::locale loc( std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ")); std::istringstream iss(str); iss.imbue(loc); boost::posix_time::ptime ptime(boost::date_time::not_a_date_time); iss >> ptime; if (ptime.is_not_a_date_time()) return 0; return (ptime - epoch).total_seconds(); } static std::string EncodeDumpString(const std::string &str) { std::stringstream ret; for (uint8_t c : str) { if (c <= 32 || c >= 128 || c == '%') { ret << '%' << HexStr(&c, &c + 1); } else { ret << c; } } return ret.str(); } std::string DecodeDumpString(const std::string &str) { std::stringstream ret; for (unsigned int pos = 0; pos < str.length(); pos++) { uint8_t c = str[pos]; if (c == '%' && pos + 2 < str.length()) { c = (((str[pos + 1] >> 6) * 9 + ((str[pos + 1] - '0') & 15)) << 4) | ((str[pos + 2] >> 6) * 9 + ((str[pos + 2] - '0') & 15)); pos += 2; } ret << c; } return ret.str(); } UniValue importprivkey(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( "importprivkey \"privkey\" ( \"label\" ) ( rescan )\n" "\nAdds a private key (as returned by dumpprivkey) to your wallet. " "Requires a new wallet backup.\n" "\nArguments:\n" "1. \"bitcoinprivkey\" (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" "\nExamples:\n" "\nDump a private key\n" + HelpExampleCli("dumpprivkey", "\"myaddress\"") + "\nImport the private key with rescan\n" + HelpExampleCli("importprivkey", "\"mykey\"") + "\nImport using a label and without rescan\n" + HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") + "\nImport using default blank label and without rescan\n" + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")); 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 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"); } CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(strSecret); if (!fGood) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CKey key = vchSecret.GetKey(); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); } CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); CKeyID vchAddress = pubkey.GetID(); { pwallet->MarkDirty(); pwallet->SetAddressBook(vchAddress, strLabel, "receive"); // Don't throw error in case a key is already there if (pwallet->HaveKey(vchAddress)) { return NullUniValue; } pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1; if (!pwallet->AddKeyPubKey(key, pubkey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } // whenever a key is imported, we need to scan the whole chain pwallet->UpdateTimeFirstKey(1); if (fRescan) { pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */); } } return NullUniValue; } UniValue abortrescan(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 0) { throw std::runtime_error("abortrescan\n" "\nStops current wallet rescan triggered e.g. " "by an importprivkey call.\n" "\nExamples:\n" "\nImport a private key\n" + HelpExampleCli("importprivkey", "\"mykey\"") + "\nAbort the running wallet rescan\n" + HelpExampleCli("abortrescan", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("abortrescan", "")); } ObserveSafeMode(); if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) { return false; } pwallet->AbortRescan(); return true; } void ImportAddress(CWallet *, const CTxDestination &dest, const std::string &strLabel); void ImportScript(CWallet *const pwallet, const CScript &script, const std::string &strLabel, bool isRedeemScript) { if (!isRedeemScript && ::IsMine(*pwallet, script) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the " "private key for this address or " "script"); } pwallet->MarkDirty(); if (!pwallet->HaveWatchOnly(script) && !pwallet->AddWatchOnly(script, 0 /* nCreateTime */)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } if (isRedeemScript) { if (!pwallet->HaveCScript(script) && !pwallet->AddCScript(script)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); } ImportAddress(pwallet, CScriptID(script), strLabel); } else { CTxDestination destination; if (ExtractDestination(script, destination)) { pwallet->SetAddressBook(destination, strLabel, "receive"); } } } void ImportAddress(CWallet *const pwallet, const CTxDestination &dest, const std::string &strLabel) { CScript script = GetScriptForDestination(dest); ImportScript(pwallet, script, strLabel, false); // add to address book or update label if (IsValidDestination(dest)) { pwallet->SetAddressBook(dest, strLabel, "receive"); } } UniValue importaddress(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) { throw std::runtime_error( "importaddress \"address\" ( \"label\" rescan p2sh )\n" "\nAdds a script (in hex) or address that can be watched as if it " "were in your wallet but cannot be used to spend. Requires a new " "wallet backup.\n" "\nArguments:\n" "1. \"script\" (string, required) The hex-encoded script " "(or address)\n" "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" "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" "\nExamples:\n" "\nImport a script with rescan\n" + HelpExampleCli("importaddress", "\"myscript\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importaddress", "\"myscript\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importaddress", "\"myscript\", \"testing\", false")); } std::string strLabel = ""; if (!request.params[1].isNull()) { strLabel = request.params[1].get_str(); } // Whether to perform rescan after import 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"); } // 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); CTxDestination dest = DecodeDestination(request.params[0].get_str(), config.GetChainParams()); 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 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->ReacceptWalletTransactions(); } return NullUniValue; } UniValue importprunedfunds(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 2) { throw std::runtime_error( "importprunedfunds\n" "\nImports funds without rescan. Corresponding address or script " "must previously be included in wallet. Aimed towards pruned " "wallets. The end-user is responsible to import additional " "transactions that subsequently spend the imported outputs or " "rescan after the point in the blockchain the transaction is " "included.\n" "\nArguments:\n" "1. \"rawtransaction\" (string, required) A raw transaction in hex " "funding an already-existing address in wallet\n" "2. \"txoutproof\" (string, required) The hex output from " "gettxoutproof that contains the transaction\n"); } CMutableTransaction tx; if (!DecodeHexTx(tx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } uint256 txid = tx.GetId(); CWalletTx wtx(pwallet, MakeTransactionRef(std::move(tx))); CDataStream ssMB(ParseHexV(request.params[1], "proof"), SER_NETWORK, PROTOCOL_VERSION); CMerkleBlock merkleBlock; ssMB >> merkleBlock; // Search partial merkle tree in proof for our transaction and index in // valid block std::vector vMatch; std::vector vIndex; unsigned int txnIndex = 0; if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) { LOCK(cs_main); if (!mapBlockIndex.count(merkleBlock.header.GetHash()) || !chainActive.Contains( mapBlockIndex[merkleBlock.header.GetHash()])) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); } std::vector::const_iterator it; if ((it = std::find(vMatch.begin(), vMatch.end(), txid)) == vMatch.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof"); } txnIndex = vIndex[it - vMatch.begin()]; } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); } wtx.nIndex = txnIndex; wtx.hashBlock = merkleBlock.header.GetHash(); LOCK2(cs_main, pwallet->cs_wallet); - if (pwallet->IsMine(wtx)) { + if (pwallet->IsMine(*wtx.tx)) { pwallet->AddToWallet(wtx, false); return NullUniValue; } throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction"); } UniValue removeprunedfunds(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "removeprunedfunds \"txid\"\n" "\nDeletes the specified transaction from the wallet. Meant for " "use with pruned wallets and as a companion to importprunedfunds. " "This will effect wallet balances.\n" "\nArguments:\n" "1. \"txid\" (string, required) The hex-encoded id of " "the transaction you are deleting\n" "\nExamples:\n" + HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1" "ce581bebf46446a512166eae762873" "4ea0a5\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("removprunedfunds", "\"a8d0c0184dde994a09ec054286f1c" "e581bebf46446a512166eae7628734e" "a0a5\"")); } LOCK2(cs_main, pwallet->cs_wallet); TxId txid; txid.SetHex(request.params[0].get_str()); std::vector txIds; txIds.push_back(txid); std::vector txIdsOut; if (pwallet->ZapSelectTx(txIds, txIdsOut) != DBErrors::LOAD_OK) { throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete the transaction."); } if (txIdsOut.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet."); } return NullUniValue; } UniValue importpubkey(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) { throw std::runtime_error( "importpubkey \"pubkey\" ( \"label\" rescan )\n" "\nAdds a public key (in hex) that can be watched as if it were in " "your wallet but cannot be used to spend. Requires a new wallet " "backup.\n" "\nArguments:\n" "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" "\nExamples:\n" "\nImport a public key with rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false")); } std::string strLabel = ""; if (!request.params[1].isNull()) { strLabel = request.params[1].get_str(); } // Whether to perform rescan after import 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"); } if (!IsHex(request.params[0].get_str())) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); } std::vector data(ParseHex(request.params[0].get_str())); CPubKey pubKey(data.begin(), data.end()); if (!pubKey.IsFullyValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); } LOCK2(cs_main, pwallet->cs_wallet); ImportAddress(pwallet, pubKey.GetID(), strLabel); ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false); if (fRescan) { pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */); pwallet->ReacceptWalletTransactions(); } return NullUniValue; } UniValue importwallet(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "importwallet \"filename\"\n" "\nImports keys from a wallet dump file (see dumpwallet). Requires " "a new wallet backup to include imported keys.\n" "\nArguments:\n" "1. \"filename\" (string, required) The wallet file\n" "\nExamples:\n" "\nDump the wallet\n" + HelpExampleCli("dumpwallet", "\"test\"") + "\nImport the wallet\n" + HelpExampleCli("importwallet", "\"test\"") + "\nImport using the json rpc call\n" + HelpExampleRpc("importwallet", "\"test\"")); } if (fPruneMode) { throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode"); } LOCK2(cs_main, pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); 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"); } int64_t nTimeBegin = chainActive.Tip()->GetBlockTime(); bool fGood = true; int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); file.seekg(0, file.beg); // show progress dialog in GUI pwallet->ShowProgress(_("Importing..."), 0); 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 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 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(); // hide progress dialog in GUI pwallet->ShowProgress("", 100); pwallet->UpdateTimeFirstKey(nTimeBegin); pwallet->RescanFromTime(nTimeBegin, false /* update */); pwallet->MarkDirty(); if (!fGood) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet"); } return NullUniValue; } UniValue dumpprivkey(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "dumpprivkey \"address\"\n" "\nReveals the private key corresponding to 'address'.\n" "Then the importprivkey can be used with this output\n" "\nArguments:\n" "1. \"address\" (string, required) The bitcoin address for the " "private key\n" "\nResult:\n" "\"key\" (string) The private key\n" "\nExamples:\n" + HelpExampleCli("dumpprivkey", "\"myaddress\"") + HelpExampleCli("importprivkey", "\"mykey\"") + HelpExampleRpc("dumpprivkey", "\"myaddress\"")); } LOCK2(cs_main, pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); std::string strAddress = request.params[0].get_str(); CTxDestination dest = DecodeDestination(strAddress, config.GetChainParams()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } const CKeyID *keyID = boost::get(&dest); if (!keyID) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); } CKey vchSecret; if (!pwallet->GetKey(*keyID, vchSecret)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); } return CBitcoinSecret(vchSecret).ToString(); } UniValue dumpwallet(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "dumpwallet \"filename\"\n" "\nDumps all wallet keys in a human-readable format to a " "server-side file. This does not allow overwriting existing " "files.\n" "Imported scripts are included in the dumpsfile, but corresponding " "addresses may not be added automatically by importwallet.\n" "Note that if your wallet contains keys which are not derived from " "your HD seed (e.g. imported keys), these are not covered by\n" "only backing up the seed itself, and must be backed up too (e.g. " "ensure you back up the whole dumpfile).\n" "\nArguments:\n" "1. \"filename\" (string, required) The filename with path " "(either absolute or relative to bitcoind)\n" "\nResult:\n" "{ (json object)\n" " \"filename\" : { (string) The filename with full " "absolute path\n" "}\n" "\nExamples:\n" + HelpExampleCli("dumpwallet", "\"test\"") + HelpExampleRpc("dumpwallet", "\"test\"")); LOCK2(cs_main, pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); fs::path filepath = request.params[0].get_str(); filepath = fs::absolute(filepath); /** * Prevent arbitrary files from being overwritten. There have been reports * that users have overwritten wallet files this way: * https://github.com/bitcoin/bitcoin/issues/9934 * It may also avoid other security issues. */ if (fs::exists(filepath)) { throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.string() + " already exists. If you are " "sure this is what you want, " "move it out of the way first"); } std::ofstream file; file.open(filepath.string().c_str()); if (!file.is_open()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); } std::map mapKeyBirth; const std::map &mapKeyPool = pwallet->GetAllReserveKeys(); pwallet->GetKeyBirthTimes(mapKeyBirth); std::set scripts = pwallet->GetCScripts(); // TODO: include scripts in GetKeyBirthTimes() output instead of separate // sort time/key pairs std::vector> vKeyBirth; for (const auto &entry : mapKeyBirth) { if (const CKeyID *keyID = boost::get(&entry.first)) { // set and test vKeyBirth.push_back(std::make_pair(entry.second, *keyID)); } } mapKeyBirth.clear(); std::sort(vKeyBirth.begin(), vKeyBirth.end()); // produce output file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD); file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString()); file << strprintf("# mined on %s\n", FormatISO8601DateTime(chainActive.Tip()->GetBlockTime())); file << "\n"; // add the base58check encoded extended master if the wallet uses HD CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID; if (!masterKeyID.IsNull()) { CKey key; if (pwallet->GetKey(masterKeyID, key)) { CExtKey masterKey; masterKey.SetMaster(key.begin(), key.size()); CBitcoinExtKey b58extkey; b58extkey.SetKey(masterKey); file << "# extended private masterkey: " << b58extkey.ToString() << "\n\n"; } } for (std::vector>::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { const CKeyID &keyid = it->second; std::string strTime = FormatISO8601DateTime(it->first); std::string strAddr = EncodeDestination(keyid); CKey key; if (pwallet->GetKey(keyid, key)) { file << strprintf("%s %s ", CBitcoinSecret(key).ToString(), strTime); if (pwallet->mapAddressBook.count(keyid)) { file << strprintf( "label=%s", EncodeDumpString(pwallet->mapAddressBook[keyid].name)); } else if (keyid == masterKeyID) { file << "hdmaster=1"; } else if (mapKeyPool.count(keyid)) { file << "reserve=1"; } else if (pwallet->mapKeyMetadata[keyid].hdKeypath == "m") { file << "inactivehdmaster=1"; } else { file << "change=1"; } file << strprintf( " # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath=" + pwallet->mapKeyMetadata[keyid].hdKeypath : "")); } } file << "\n"; for (const CScriptID &scriptid : scripts) { CScript script; std::string create_time = "0"; std::string address = EncodeDestination(scriptid); // get birth times for scripts with metadata auto it = pwallet->m_script_metadata.find(scriptid); if (it != pwallet->m_script_metadata.end()) { create_time = FormatISO8601DateTime(it->second.nCreateTime); } if (pwallet->GetCScript(scriptid, script)) { file << strprintf("%s %s script=1", HexStr(script.begin(), script.end()), create_time); file << strprintf(" # addr=%s\n", address); } } file << "\n"; file << "# End of dump\n"; file.close(); UniValue reply(UniValue::VOBJ); reply.pushKV("filename", filepath.string()); return reply; } UniValue ProcessImport(CWallet *const pwallet, const UniValue &data, const int64_t timestamp) { try { bool success = false; // Required fields. const UniValue &scriptPubKey = data["scriptPubKey"]; // Should have script or JSON with "address". if (!(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address")) && !(scriptPubKey.getType() == UniValue::VSTR)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scriptPubKey"); } // Optional fields. const std::string &strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; const UniValue &pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); const UniValue &keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false; const std::string &label = data.exists("label") && !internal ? data["label"].get_str() : ""; bool isScript = scriptPubKey.getType() == UniValue::VSTR; bool isP2SH = strRedeemScript.length() > 0; const std::string &output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); // Parse the output. CScript script; CTxDestination dest; if (!isScript) { dest = DecodeDestination(output, pwallet->chainParams); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); } script = GetScriptForDestination(dest); } else { if (!IsHex(output)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey"); } std::vector vData(ParseHex(output)); script = CScript(vData.begin(), vData.end()); } // Watchonly and private keys if (watchOnly && keys.size()) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Incompatibility found between watchonly and keys"); } // Internal + Label if (internal && data.exists("label")) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Incompatibility found between internal and label"); } // Not having Internal + Script if (!internal && isScript) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set for hex scriptPubKey"); } // Keys / PubKeys size check. if (!isP2SH && (keys.size() > 1 || pubKeys.size() > 1)) { // Address / scriptPubKey throw JSONRPCError(RPC_INVALID_PARAMETER, "More than private key given for one address"); } // Invalid P2SH redeemScript if (isP2SH && !IsHex(strRedeemScript)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script"); } // Process. // // P2SH if (isP2SH) { // Import redeem script. std::vector vData(ParseHex(strRedeemScript)); CScript redeemScript = CScript(vData.begin(), vData.end()); // Invalid P2SH address if (!script.IsPayToScriptHash()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid P2SH address / script"); } pwallet->MarkDirty(); if (!pwallet->AddWatchOnly(redeemScript, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } if (!pwallet->HaveCScript(redeemScript) && !pwallet->AddCScript(redeemScript)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); } CTxDestination redeem_dest = CScriptID(redeemScript); CScript redeemDestination = GetScriptForDestination(redeem_dest); if (::IsMine(*pwallet, redeemDestination) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private " "key for this address or script"); } pwallet->MarkDirty(); if (!pwallet->AddWatchOnly(redeemDestination, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } // add to address book or update label if (IsValidDestination(dest)) { pwallet->SetAddressBook(dest, label, "receive"); } // Import private keys. if (keys.size()) { for (size_t i = 0; i < keys.size(); i++) { const std::string &privkey = keys[i].get_str(); CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(privkey); if (!fGood) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CKey key = vchSecret.GetKey(); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); } CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); CKeyID vchAddress = pubkey.GetID(); pwallet->MarkDirty(); pwallet->SetAddressBook(vchAddress, label, "receive"); if (pwallet->HaveKey(vchAddress)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key"); } pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp; if (!pwallet->AddKeyPubKey(key, pubkey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } pwallet->UpdateTimeFirstKey(timestamp); } } success = true; } else { // Import public keys. if (pubKeys.size() && keys.size() == 0) { const std::string &strPubKey = pubKeys[0].get_str(); if (!IsHex(strPubKey)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); } std::vector vData(ParseHex(strPubKey)); CPubKey pubKey(vData.begin(), vData.end()); if (!pubKey.IsFullyValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); } CTxDestination pubkey_dest = pubKey.GetID(); // Consistency check. if (!isScript && !(pubkey_dest == dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); } // Consistency check. if (isScript) { CTxDestination destination; if (ExtractDestination(script, destination)) { if (!(destination == pubkey_dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); } } } CScript pubKeyScript = GetScriptForDestination(pubkey_dest); if (::IsMine(*pwallet, pubKeyScript) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already " "contains the private " "key for this address " "or script"); } pwallet->MarkDirty(); if (!pwallet->AddWatchOnly(pubKeyScript, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } // add to address book or update label if (IsValidDestination(pubkey_dest)) { pwallet->SetAddressBook(pubkey_dest, label, "receive"); } // TODO Is this necessary? CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey); if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already " "contains the private " "key for this address " "or script"); } pwallet->MarkDirty(); if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } success = true; } // Import private keys. if (keys.size()) { const std::string &strPrivkey = keys[0].get_str(); // Checks. CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(strPrivkey); if (!fGood) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CKey key = vchSecret.GetKey(); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); } CPubKey pubKey = key.GetPubKey(); assert(key.VerifyPubKey(pubKey)); CTxDestination pubkey_dest = pubKey.GetID(); // Consistency check. if (!isScript && !(pubkey_dest == dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); } // Consistency check. if (isScript) { CTxDestination destination; if (ExtractDestination(script, destination)) { if (!(destination == pubkey_dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed"); } } } CKeyID vchAddress = pubKey.GetID(); pwallet->MarkDirty(); pwallet->SetAddressBook(vchAddress, label, "receive"); if (pwallet->HaveKey(vchAddress)) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already " "contains the private " "key for this address " "or script"); } pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp; if (!pwallet->AddKeyPubKey(key, pubKey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } pwallet->UpdateTimeFirstKey(timestamp); success = true; } // Import scriptPubKey only. if (pubKeys.size() == 0 && keys.size() == 0) { if (::IsMine(*pwallet, script) == ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already " "contains the private " "key for this address " "or script"); } pwallet->MarkDirty(); if (!pwallet->AddWatchOnly(script, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } if (scriptPubKey.getType() == UniValue::VOBJ) { // add to address book or update label if (IsValidDestination(dest)) { pwallet->SetAddressBook(dest, label, "receive"); } } success = true; } } UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(success)); return result; } catch (const UniValue &e) { UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); result.pushKV("error", e); return result; } catch (...) { UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); return result; } } int64_t GetImportTimestamp(const UniValue &data, int64_t now) { if (data.exists("timestamp")) { const UniValue ×tamp = data["timestamp"]; if (timestamp.isNum()) { return timestamp.get_int64(); } else if (timestamp.isStr() && timestamp.get_str() == "now") { return now; } throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp " "value for key. got type %s", uvTypeName(timestamp.type()))); } throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key"); } UniValue importmulti(const Config &config, const JSONRPCRequest &mainRequest) { CWallet *const pwallet = GetWalletForJSONRPCRequest(mainRequest); if (!EnsureWalletIsAvailable(pwallet, mainRequest.fHelp)) { return NullUniValue; } // clang-format off if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) { throw std::runtime_error( "importmulti \"requests\" ( \"options\" )\n\n" "Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.\n\n" "Arguments:\n" "1. requests (array, required) Data to be imported\n" " [ (array of json objects)\n" " {\n" " \"scriptPubKey\": \"