diff options
Diffstat (limited to 'src/wallet/wallet.cpp')
-rw-r--r-- | src/wallet/wallet.cpp | 137 |
1 files changed, 122 insertions, 15 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 2775f4def3..b4715622cf 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -411,6 +411,13 @@ set<uint256> CWallet::GetConflicts(const uint256& txid) const return result; } +bool CWallet::HasWalletSpend(const uint256& txid) const +{ + AssertLockHeld(cs_wallet); + auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0)); + return (iter != mapTxSpends.end() && iter->first.hash == txid); +} + void CWallet::Flush(bool shutdown) { bitdb.Flush(shutdown); @@ -826,6 +833,35 @@ void CWallet::MarkDirty() } } +bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) +{ + LOCK(cs_wallet); + + auto mi = mapWallet.find(originalHash); + + // There is a bug if MarkReplaced is not called on an existing wallet transaction. + assert(mi != mapWallet.end()); + + CWalletTx& wtx = (*mi).second; + + // Ensure for now that we're not overwriting data + assert(wtx.mapValue.count("replaced_by_txid") == 0); + + wtx.mapValue["replaced_by_txid"] = newHash.ToString(); + + CWalletDB walletdb(strWalletFile, "r+"); + + bool success = true; + if (!walletdb.WriteTx(wtx)) { + LogPrintf("%s: Updating walletdb tx %s failed", __func__, wtx.GetHash().ToString()); + success = false; + } + + NotifyTransactionChanged(this, originalHash, CT_UPDATED); + + return success; +} + bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) { LOCK(cs_wallet); @@ -967,9 +1003,17 @@ bool CWallet::LoadToWallet(const CWalletTx& wtxIn) } /** - * Add a transaction to the wallet, or update it. - * pblock is optional, but should be provided if the transaction is known to be in a block. + * Add a transaction to the wallet, or update it. pIndex and posInBlock should + * be set when the transaction was known to be included in a block. When + * posInBlock = SYNC_TRANSACTION_NOT_IN_BLOCK (-1) , then wallet state is not + * updated in AddToWallet, but notifications happen and cached balances are + * marked dirty. * If fUpdate is true, existing transactions will be updated. + * TODO: One exception to this is that the abandoned state is cleared under the + * assumption that any further notification of a transaction that was considered + * abandoned is an indication that it is not safe to be considered abandoned. + * Abandoned state should probably be more carefuly tracked via different + * posInBlock signals or by checking mempool presence when necessary. */ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) { @@ -1154,6 +1198,8 @@ isminetype CWallet::IsMine(const CTxIn &txin) const return ISMINE_NO; } +// Note that this function doesn't distinguish between a 0-valued input, +// and a not-"is mine" (according to the filter) input. CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const { { @@ -1236,6 +1282,27 @@ CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) co return nDebit; } +bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const +{ + LOCK(cs_wallet); + + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + auto mi = mapWallet.find(txin.prevout.hash); + if (mi == mapWallet.end()) + return false; // any unknown inputs can't be from us + + const CWalletTx& prev = (*mi).second; + + if (txin.prevout.n >= prev.tx->vout.size()) + return false; // invalid input! + + if (!(IsMine(prev.tx->vout[txin.prevout.n]) & filter)) + return false; + } + return true; +} + CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const { CAmount nCredit = 0; @@ -1479,12 +1546,12 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) pindex = chainActive.Next(pindex); ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup - double dProgressStart = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false); - double dProgressTip = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.Tip(), false); + double dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex); + double dProgressTip = GuessVerificationProgress(chainParams.TxData(), chainActive.Tip()); while (pindex) { if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) - ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false) - dProgressStart) / (dProgressTip - dProgressStart) * 100)))); + ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((GuessVerificationProgress(chainParams.TxData(), pindex) - dProgressStart) / (dProgressTip - dProgressStart) * 100)))); CBlock block; ReadBlockFromDisk(block, pindex, Params().GetConsensus()); @@ -1497,7 +1564,7 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) pindex = chainActive.Next(pindex); if (GetTime() >= nNow + 60) { nNow = GetTime(); - LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex)); + LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex)); } } ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI @@ -1958,6 +2025,37 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const if (nDepth == 0 && !pcoin->InMempool()) continue; + // We should not consider coins from transactions that are replacing + // other transactions. + // + // Example: There is a transaction A which is replaced by bumpfee + // transaction B. In this case, we want to prevent creation of + // a transaction B' which spends an output of B. + // + // Reason: If transaction A were initially confirmed, transactions B + // and B' would no longer be valid, so the user would have to create + // a new transaction C to replace B'. However, in the case of a + // one-block reorg, transactions B' and C might BOTH be accepted, + // when the user only wanted one of them. Specifically, there could + // be a 1-block reorg away from the chain where transactions A and C + // were accepted to another chain where B, B', and C were all + // accepted. + if (nDepth == 0 && fOnlyConfirmed && pcoin->mapValue.count("replaces_txid")) { + continue; + } + + // Similarly, we should not consider coins from transactions that + // have been replaced. In the example above, we would want to prevent + // creation of a transaction A' spending an output of A, because if + // transaction B were initially confirmed, conflicting with A and + // A', we wouldn't want to the user to create a transaction D + // intending to replace A', but potentially resulting in a scenario + // where A, A', and D could all be accepted (instead of just B and + // D, or just A and A' like the user would want). + if (nDepth == 0 && fOnlyConfirmed && pcoin->mapValue.count("replaced_by_txid")) { + continue; + } + for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) { isminetype mine = IsMine(pcoin->tx->vout[i]); if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && @@ -2192,14 +2290,15 @@ bool CWallet::SelectCoins(const vector<COutput>& vAvailableCoins, const CAmount& return res; } -bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const CTxDestination& destChange) +bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, bool keepReserveKey, const CTxDestination& destChange) { vector<CRecipient> vecSend; // Turn the txout set into a CRecipient vector - BOOST_FOREACH(const CTxOut& txOut, tx.vout) + for (size_t idx = 0; idx < tx.vout.size(); idx++) { - CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, false}; + const CTxOut& txOut = tx.vout[idx]; + CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1}; vecSend.push_back(recipient); } @@ -2221,6 +2320,10 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov if (nChangePosInOut != -1) tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]); + // Copy output sizes from new transaction; they may have had the fee subtracted from them + for (unsigned int idx = 0; idx < tx.vout.size(); idx++) + tx.vout[idx].nValue = wtx.tx->vout[idx].nValue; + // Add new txins (keeping original txin scriptSig/order) BOOST_FOREACH(const CTxIn& txin, wtx.tx->vin) { @@ -2236,6 +2339,10 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov } } + // optionally keep the change output key + if (keepReserveKey) + reservekey.KeepKey(); + return true; } @@ -2336,7 +2443,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt } } - if (txout.IsDust(::minRelayTxFee)) + if (txout.IsDust(dustRelayFee)) { if (recipient.fSubtractFeeFromAmount && nFeeRet > 0) { @@ -2414,16 +2521,16 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt // We do not move dust-change to fees, because the sender would end up paying more than requested. // This would be against the purpose of the all-inclusive feature. // So instead we raise the change and deduct from the recipient. - if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(::minRelayTxFee)) + if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(dustRelayFee)) { - CAmount nDust = newTxOut.GetDustThreshold(::minRelayTxFee) - newTxOut.nValue; + CAmount nDust = newTxOut.GetDustThreshold(dustRelayFee) - newTxOut.nValue; newTxOut.nValue += nDust; // raise change until no more dust for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient { if (vecSend[i].fSubtractFeeFromAmount) { txNew.vout[i].nValue -= nDust; - if (txNew.vout[i].IsDust(::minRelayTxFee)) + if (txNew.vout[i].IsDust(dustRelayFee)) { strFailReason = _("The transaction amount is too small to send after the fee has been deducted"); return false; @@ -2435,7 +2542,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt // Never create dust outputs; if we would, just // add the dust to the fee. - if (newTxOut.IsDust(::minRelayTxFee)) + if (newTxOut.IsDust(dustRelayFee)) { nChangePosInOut = -1; nFeeRet += nChange; @@ -3847,5 +3954,5 @@ int CMerkleTx::GetBlocksToMaturity() const bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state) { - return ::AcceptToMemoryPool(mempool, state, tx, true, NULL, false, nAbsurdFee); + return ::AcceptToMemoryPool(mempool, state, tx, true, NULL, NULL, false, nAbsurdFee); } |