diff options
Diffstat (limited to 'src/wallet/wallet.cpp')
-rw-r--r-- | src/wallet/wallet.cpp | 229 |
1 files changed, 144 insertions, 85 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 63d0a3c0c4..5689cc7b0c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -220,6 +220,10 @@ bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigne return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); } +/** + * Update wallet first key creation time. This should be called whenever keys + * are added to the wallet, with the oldest key creation time. + */ void CWallet::UpdateTimeFirstKey(int64_t nCreateTime) { AssertLockHeld(cs_wallet); @@ -1468,6 +1472,34 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, } /** + * Scan active chain for relevant transactions after importing keys. This should + * be called whenever new keys are added to the wallet, with the oldest key + * creation time. + * + * @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) +{ + 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); + + if (startBlock) { + const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, update); + if (failedBlock) { + return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1; + } + } + return startTime; +} + +/** * Scan the block chain (starting in pindexStart) for transactions * from or to us. If fUpdate is true, found transactions that already * exist in the wallet will be updated. @@ -1488,11 +1520,6 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool f fAbortRescan = false; fScanningWallet = true; - // no need to read and scan block, if block was created before - // our wallet birthday (as adjusted for block time variability) - while (pindex && nTimeFirstKey && (pindex->GetBlockTime() < (nTimeFirstKey - TIMESTAMP_WINDOW))) - pindex = chainActive.Next(pindex); - 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()); @@ -1691,7 +1718,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const const CTxOut &txout = tx->vout[i]; nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); if (!MoneyRange(nCredit)) - throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range"); + throw std::runtime_error(std::string(__func__) + " : value out of range"); } } @@ -1734,7 +1761,7 @@ CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const const CTxOut &txout = tx->vout[i]; nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY); if (!MoneyRange(nCredit)) - throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range"); + throw std::runtime_error(std::string(__func__) + ": value out of range"); } } @@ -2542,7 +2569,43 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT std::vector<COutput> vAvailableCoins; AvailableCoins(vAvailableCoins, true, coinControl); + // Create change script that will be used if we need change + // TODO: pass in scriptChange instead of reservekey so + // change transaction isn't always pay-to-bitcoin-address + CScript scriptChange; + + // coin control: send change to custom address + if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange)) + scriptChange = GetScriptForDestination(coinControl->destChange); + + // no coin control: send change to newly generated address + else + { + // Note: We use a new key here to keep it from being obvious which side is the change. + // The drawback is that by not reusing a previous key, the change may be lost if a + // backup is restored, if the backup doesn't have the new private key for the change. + // If we reused the old key, it would be possible to add code to look for and + // rediscover unknown transactions that were written with keys of ours to recover + // post-backup change. + + // Reserve a new key pair from key pool + CPubKey vchPubKey; + bool ret; + ret = reservekey.GetReservedKey(vchPubKey, true); + if (!ret) + { + strFailReason = _("Keypool ran out, please call keypoolrefill first"); + return false; + } + + scriptChange = GetScriptForDestination(vchPubKey.GetID()); + } + CTxOut change_prototype_txout(0, scriptChange); + size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0); + nFeeRet = 0; + bool pick_new_inputs = true; + CAmount nValueIn = 0; // Start with no fee and loop until there is enough fee while (true) { @@ -2588,80 +2651,29 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT } // Choose coins to use - CAmount nValueIn = 0; - setCoins.clear(); - if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl)) - { - strFailReason = _("Insufficient funds"); - return false; + if (pick_new_inputs) { + nValueIn = 0; + setCoins.clear(); + if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl)) + { + strFailReason = _("Insufficient funds"); + return false; + } } const CAmount nChange = nValueIn - nValueToSelect; + if (nChange > 0) { // Fill a vout to ourself - // TODO: pass in scriptChange instead of reservekey so - // change transaction isn't always pay-to-bitcoin-address - CScript scriptChange; - - // coin control: send change to custom address - if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange)) - scriptChange = GetScriptForDestination(coinControl->destChange); - - // no coin control: send change to newly generated address - else - { - // Note: We use a new key here to keep it from being obvious which side is the change. - // The drawback is that by not reusing a previous key, the change may be lost if a - // backup is restored, if the backup doesn't have the new private key for the change. - // If we reused the old key, it would be possible to add code to look for and - // rediscover unknown transactions that were written with keys of ours to recover - // post-backup change. - - // Reserve a new key pair from key pool - CPubKey vchPubKey; - bool ret; - ret = reservekey.GetReservedKey(vchPubKey, true); - if (!ret) - { - strFailReason = _("Keypool ran out, please call keypoolrefill first"); - return false; - } - - scriptChange = GetScriptForDestination(vchPubKey.GetID()); - } - CTxOut newTxOut(nChange, scriptChange); - // We do not move dust-change to fees, because the sender would end up paying more than requested. - // This would be against the purpose of the all-inclusive feature. - // So instead we raise the change and deduct from the recipient. - if (nSubtractFeeFromAmount > 0 && IsDust(newTxOut, ::dustRelayFee)) - { - CAmount nDust = GetDustThreshold(newTxOut, ::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 (IsDust(txNew.vout[i], ::dustRelayFee)) - { - strFailReason = _("The transaction amount is too small to send after the fee has been deducted"); - return false; - } - break; - } - } - } - // Never create dust outputs; if we would, just // add the dust to the fee. if (IsDust(newTxOut, ::dustRelayFee)) { nChangePosInOut = -1; nFeeRet += nChange; - reservekey.ReturnKey(); } else { @@ -2680,7 +2692,6 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT txNew.vout.insert(position, newTxOut); } } else { - reservekey.ReturnKey(); nChangePosInOut = -1; } @@ -2719,7 +2730,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT if (coinControl && coinControl->nConfirmTarget > 0) currentConfirmationTarget = coinControl->nConfirmTarget; - CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator, &feeCalc); + // Allow to override the default fee estimate mode over the CoinControl instance + bool conservative_estimate = CalculateEstimateType(coinControl ? coinControl->m_fee_mode : FeeEstimateMode::UNSET, rbf); + + CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator, &feeCalc, false /* ignoreGlobalPayTxFee */, conservative_estimate); if (coinControl && coinControl->fOverrideFeeRate) nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes); @@ -2732,16 +2746,30 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT } if (nFeeRet >= nFeeNeeded) { - // Reduce fee to only the needed amount if we have change - // output to increase. This prevents potential overpayment - // in fees if the coins selected to meet nFeeNeeded result - // in a transaction that requires less fee than the prior - // iteration. + // Reduce fee to only the needed amount if possible. This + // prevents potential overpayment in fees if the coins + // selected to meet nFeeNeeded result in a transaction that + // requires less fee than the prior iteration. + // TODO: The case where nSubtractFeeFromAmount > 0 remains // to be addressed because it requires returning the fee to // the payees and not the change output. - // TODO: The case where there is no change output remains - // to be addressed so we avoid creating too small an output. + + // If we have no change and a big enough excess fee, then + // try to construct transaction again only without picking + // new inputs. We now know we only need the smaller fee + // (because of reduced tx size) and so we should add a + // change output. Only try this once. + CAmount fee_needed_for_change = GetMinimumFee(change_prototype_size, currentConfirmationTarget, ::mempool, ::feeEstimator, nullptr, false /* ignoreGlobalPayTxFee */, conservative_estimate); + CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, ::dustRelayFee); + CAmount max_excess_fee = fee_needed_for_change + minimum_value_for_change; + if (nFeeRet > nFeeNeeded + max_excess_fee && nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) { + pick_new_inputs = false; + nFeeRet = nFeeNeeded + fee_needed_for_change; + continue; + } + + // If we have change output already, just increase it if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { CAmount extraFeePaid = nFeeRet - nFeeNeeded; std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut; @@ -2750,6 +2778,12 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT } break; // Done, enough fee included. } + else if (!pick_new_inputs) { + // This shouldn't happen, we should have had enough excess + // fee to pay for the new output and still meet nFeeNeeded + strFailReason = _("Transaction fee and change calculation failed"); + return false; + } // Try to reduce change to include necessary fee if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { @@ -2769,6 +2803,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT } } + if (nChangePosInOut == -1) reservekey.ReturnKey(); // Return any reserved key if we don't have change + if (sign) { CTransaction txNewConst(txNew); @@ -2900,13 +2936,13 @@ CAmount CWallet::GetRequiredFee(unsigned int nTxBytes) return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes)); } -CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc, bool ignoreGlobalPayTxFee) +CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc, bool ignoreGlobalPayTxFee, bool conservative_estimate) { // payTxFee is the user-set global for desired feerate CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes); // User didn't set: use -txconfirmtarget to estimate... if (nFeeNeeded == 0 || ignoreGlobalPayTxFee) { - nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, feeCalc, pool, true).GetFee(nTxBytes); + nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, feeCalc, pool, conservative_estimate).GetFee(nTxBytes); // ... unless we don't have enough mempool data for estimatefee, then use fallbackFee if (nFeeNeeded == 0) { nFeeNeeded = fallbackFee.GetFee(nTxBytes); @@ -3868,11 +3904,11 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) else if (IsArgSet("-usehd")) { bool useHD = GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET); if (walletInstance->IsHDEnabled() && !useHD) { - InitError(strprintf(_("Error loading %s: You can't disable HD on a already existing HD wallet"), walletFile)); + InitError(strprintf(_("Error loading %s: You can't disable HD on an already existing HD wallet"), walletFile)); return NULL; } if (!walletInstance->IsHDEnabled() && useHD) { - InitError(strprintf(_("Error loading %s: You can't enable HD on a already existing non-HD wallet"), walletFile)); + InitError(strprintf(_("Error loading %s: You can't enable HD on an already existing non-HD wallet"), walletFile)); return NULL; } } @@ -3892,7 +3928,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) if (chainActive.Tip() && chainActive.Tip() != pindexRescan) { //We can't rescan beyond non-pruned blocks, stop and throw an error - //this might happen if a user uses a old wallet within a pruned node + //this might happen if a user uses an old wallet within a pruned node // or if he ran -disablewallet for a longer time, then decided to re-enable if (fPruneMode) { @@ -3908,6 +3944,13 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) uiInterface.InitMessage(_("Rescanning...")); LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); + + // No need to read and scan block if block was created before + // our wallet birthday (as adjusted for block time variability) + while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) { + pindexRescan = chainActive.Next(pindexRescan); + } + nStart = GetTimeMillis(); walletInstance->ScanForWalletTransactions(pindexRescan, true); LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); @@ -3995,20 +4038,24 @@ bool CWallet::ParameterInteraction() LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__); } - if (GetBoolArg("-salvagewallet", false) && SoftSetBoolArg("-rescan", true)) { + if (GetBoolArg("-salvagewallet", false)) { if (is_multiwallet) { return InitError(strprintf("%s is only allowed with a single wallet file", "-salvagewallet")); } // Rewrite just private keys: rescan to find transactions - LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__); + if (SoftSetBoolArg("-rescan", true)) { + LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__); + } } // -zapwallettx implies a rescan - if (GetBoolArg("-zapwallettxes", false) && SoftSetBoolArg("-rescan", true)) { + if (GetBoolArg("-zapwallettxes", false)) { if (is_multiwallet) { return InitError(strprintf("%s is only allowed with a single wallet file", "-zapwallettxes")); } - LogPrintf("%s: parameter interaction: -zapwallettxes=<mode> -> setting -rescan=1\n", __func__); + if (SoftSetBoolArg("-rescan", true)) { + LogPrintf("%s: parameter interaction: -zapwallettxes=<mode> -> setting -rescan=1\n", __func__); + } } if (is_multiwallet) { @@ -4147,3 +4194,15 @@ bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& { return ::AcceptToMemoryPool(mempool, state, tx, true, NULL, NULL, false, nAbsurdFee); } + +bool CalculateEstimateType(FeeEstimateMode mode, bool opt_in_rbf) { + switch (mode) { + case FeeEstimateMode::UNSET: + return !opt_in_rbf; // Allow for lower fees if RBF is an option + case FeeEstimateMode::CONSERVATIVE: + return true; + case FeeEstimateMode::ECONOMICAL: + return false; + } + return true; +} |