aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/wallet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/wallet.cpp')
-rw-r--r--src/wallet/wallet.cpp886
1 files changed, 530 insertions, 356 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index dafd708d09..d63c40d2ff 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,19 +1,20 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2016 The Bitcoin Core developers
+// Copyright (c) 2009-2017 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <wallet/wallet.h>
-#include <base58.h>
#include <checkpoints.h>
#include <chain.h>
#include <wallet/coincontrol.h>
+#include <wallet/coinselection.h>
#include <consensus/consensus.h>
#include <consensus/validation.h>
#include <fs.h>
#include <wallet/init.h>
#include <key.h>
+#include <key_io.h>
#include <keystore.h>
#include <validation.h>
#include <net.h>
@@ -34,7 +35,6 @@
#include <future>
#include <boost/algorithm/string/replace.hpp>
-#include <boost/thread.hpp>
std::vector<CWalletRef> vpwallets;
/** Transaction fee set by the user */
@@ -42,8 +42,8 @@ CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET;
bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE;
bool fWalletRbf = DEFAULT_WALLET_RBF;
+bool g_wallet_allow_fallback_fee = true; //<! will be defined via chainparams
-const char * DEFAULT_WALLET_DAT = "wallet.dat";
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
/**
@@ -67,15 +67,6 @@ const uint256 CMerkleTx::ABANDON_HASH(uint256S("00000000000000000000000000000000
* @{
*/
-struct CompareValueOnly
-{
- bool operator()(const CInputCoin& t1,
- const CInputCoin& t2) const
- {
- return t1.txout.nValue < t2.txout.nValue;
- }
-};
-
std::string COutput::ToString() const
{
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
@@ -529,14 +520,11 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran
int nMinOrderPos = std::numeric_limits<int>::max();
const CWalletTx* copyFrom = nullptr;
- for (TxSpends::iterator it = range.first; it != range.second; ++it)
- {
- const uint256& hash = it->second;
- int n = mapWallet[hash].nOrderPos;
- if (n < nMinOrderPos)
- {
- nMinOrderPos = n;
- copyFrom = &mapWallet[hash];
+ for (TxSpends::iterator it = range.first; it != range.second; ++it) {
+ const CWalletTx* wtx = &mapWallet.at(it->second);
+ if (wtx->nOrderPos < nMinOrderPos) {
+ nMinOrderPos = wtx->nOrderPos;;
+ copyFrom = wtx;
}
}
@@ -546,7 +534,7 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran
for (TxSpends::iterator it = range.first; it != range.second; ++it)
{
const uint256& hash = it->second;
- CWalletTx* copyTo = &mapWallet[hash];
+ CWalletTx* copyTo = &mapWallet.at(hash);
if (copyFrom == copyTo) continue;
assert(copyFrom && "Oldest wallet transaction in range assumed to have been found.");
if (!copyFrom->IsEquivalentTo(*copyTo)) continue;
@@ -821,7 +809,7 @@ bool CWallet::AccountMove(std::string strFrom, std::string strTo, CAmount nAmoun
return true;
}
-bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bForceNew)
+bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount, bool bForceNew)
{
CWalletDB walletdb(*dbw);
@@ -832,8 +820,8 @@ bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bFo
if (!account.vchPubKey.IsValid())
bForceNew = true;
else {
- // Check if the current key has been used
- CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID());
+ // Check if the current key has been used (TODO: check other addresses with the same key)
+ CScript scriptPubKey = GetScriptForDestination(GetDestinationForKey(account.vchPubKey, m_default_address_type));
for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin();
it != mapWallet.end() && account.vchPubKey.IsValid();
++it)
@@ -850,12 +838,14 @@ bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bFo
if (!GetKeyFromPool(account.vchPubKey, false))
return false;
- SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive");
+ LearnRelatedScripts(account.vchPubKey, m_default_address_type);
+ dest = GetDestinationForKey(account.vchPubKey, m_default_address_type);
+ SetAddressBook(dest, strAccount, "receive");
walletdb.WriteAccount(strAccount, account);
+ } else {
+ dest = GetDestinationForKey(account.vchPubKey, m_default_address_type);
}
- pubKey = account.vchPubKey;
-
return true;
}
@@ -975,7 +965,8 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
if (!strCmd.empty())
{
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
- boost::thread t(runCommand, strCmd); // thread runs free
+ std::thread t(runCommand, strCmd);
+ t.detach(); // thread runs free
}
return true;
@@ -984,9 +975,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
bool CWallet::LoadToWallet(const CWalletTx& wtxIn)
{
uint256 hash = wtxIn.GetHash();
-
- mapWallet[hash] = wtxIn;
- CWalletTx& wtx = mapWallet[hash];
+ CWalletTx& wtx = mapWallet.emplace(hash, wtxIn).first->second;
wtx.BindWallet(this);
wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr)));
AddToSpends(hash);
@@ -1079,7 +1068,7 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
{
LOCK2(cs_main, cs_wallet);
const CWalletTx* wtx = GetWalletTx(hashTx);
- return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() <= 0 && !wtx->InMempool();
+ return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool();
}
bool CWallet::AbandonTransaction(const uint256& hashTx)
@@ -1095,7 +1084,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
auto it = mapWallet.find(hashTx);
assert(it != mapWallet.end());
CWalletTx& origtx = it->second;
- if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) {
+ if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) {
return false;
}
@@ -1148,11 +1137,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
LOCK2(cs_main, cs_wallet);
int conflictconfirms = 0;
- if (mapBlockIndex.count(hashBlock)) {
- CBlockIndex* pindex = mapBlockIndex[hashBlock];
- if (chainActive.Contains(pindex)) {
- conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1);
- }
+ CBlockIndex* pindex = LookupBlockIndex(hashBlock);
+ if (pindex && chainActive.Contains(pindex)) {
+ conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1);
}
// If number of conflict confirms cannot be determined, this means
// that the block is still unknown or not yet part of the main chain,
@@ -1280,7 +1267,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() {
// chainActive.Tip()...
// We could also take cs_wallet here, and call m_last_block_processed
// protected by cs_wallet instead of cs_main, but as long as we need
- // cs_main here anyway, its easier to just call it cs_main-protected.
+ // cs_main here anyway, it's easier to just call it cs_main-protected.
LOCK(cs_main);
const CBlockIndex* initialChainTip = chainActive.Tip();
@@ -1292,12 +1279,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() {
// ...otherwise put a callback in the validation interface queue and wait
// for the queue to drain enough to execute it (indicating we are caught up
// at least with the time we entered this function).
-
- std::promise<void> promise;
- CallFunctionInValidationInterfaceQueue([&promise] {
- promise.set_value();
- });
- promise.get_future().wait();
+ SyncWithValidationInterfaceQueue();
}
@@ -1549,6 +1531,79 @@ int CWalletTx::GetRequestCount() const
return nRequests;
}
+// Helper for producing a max-sized low-S signature (eg 72 bytes)
+bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout) const
+{
+ // Fill in dummy signatures for fee calculation.
+ const CScript& scriptPubKey = txout.scriptPubKey;
+ SignatureData sigdata;
+
+ if (!ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata))
+ {
+ return false;
+ } else {
+ UpdateInput(tx_in, sigdata);
+ }
+ return true;
+}
+
+// Helper for producing a bunch of max-sized low-S signatures (eg 72 bytes)
+bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts) const
+{
+ // Fill in dummy signatures for fee calculation.
+ int nIn = 0;
+ for (const auto& txout : txouts)
+ {
+ if (!DummySignInput(txNew.vin[nIn], txout)) {
+ return false;
+ }
+
+ nIn++;
+ }
+ return true;
+}
+
+int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet)
+{
+ std::vector<CTxOut> txouts;
+ // Look up the inputs. We should have already checked that this transaction
+ // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
+ // wallet, with a valid index into the vout array, and the ability to sign.
+ for (auto& input : tx.vin) {
+ const auto mi = wallet->mapWallet.find(input.prevout.hash);
+ if (mi == wallet->mapWallet.end()) {
+ return -1;
+ }
+ assert(input.prevout.n < mi->second.tx->vout.size());
+ txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
+ }
+ return CalculateMaximumSignedTxSize(tx, wallet, txouts);
+}
+
+// txouts needs to be in the order of tx.vin
+int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts)
+{
+ CMutableTransaction txNew(tx);
+ if (!wallet->DummySignTx(txNew, txouts)) {
+ // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
+ // implies that we can sign for every input.
+ return -1;
+ }
+ return GetVirtualTransactionSize(txNew);
+}
+
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet)
+{
+ CMutableTransaction txn;
+ txn.vin.push_back(CTxIn(COutPoint()));
+ if (!wallet->DummySignInput(txn.vin[0], txout)) {
+ // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
+ // implies that we can sign for every input.
+ return -1;
+ }
+ return GetVirtualTransactionInputSize(txn.vin[0]);
+}
+
void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
std::list<COutputEntry>& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const
{
@@ -1613,19 +1668,20 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
* @return Earliest timestamp that could be successfully scanned from. Timestamp
* returned will be higher than startTime if relevant blocks could not be read.
*/
-int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
+int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update)
{
- AssertLockHeld(cs_main);
- AssertLockHeld(cs_wallet);
-
// Find starting block. May be null if nCreateTime is greater than the
// highest blockchain timestamp, in which case there is nothing that needs
// to be scanned.
- CBlockIndex* const startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
- LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
+ CBlockIndex* startBlock = nullptr;
+ {
+ LOCK(cs_main);
+ startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
+ LogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
+ }
if (startBlock) {
- const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, update);
+ const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update);
if (failedBlock) {
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
}
@@ -1644,12 +1700,17 @@ int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
*
* If pindexStop is not a nullptr, the scan will stop at the block-index
* defined by pindexStop
+ *
+ * Caller needs to make sure pindexStop (and the optional pindexStart) are on
+ * the main chain after to the addition of any new keys you want to detect
+ * transactions for.
*/
-CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, bool fUpdate)
+CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver &reserver, bool fUpdate)
{
int64_t nNow = GetTime();
const CChainParams& chainParams = Params();
+ assert(reserver.isReserved());
if (pindexStop) {
assert(pindexStop->nHeight >= pindexStart->nHeight);
}
@@ -1657,24 +1718,37 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
CBlockIndex* pindex = pindexStart;
CBlockIndex* ret = nullptr;
{
- LOCK2(cs_main, cs_wallet);
fAbortRescan = false;
- fScanningWallet = true;
-
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
- double dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
- double dProgressTip = GuessVerificationProgress(chainParams.TxData(), chainActive.Tip());
+ CBlockIndex* tip = nullptr;
+ double dProgressStart;
+ double dProgressTip;
+ {
+ LOCK(cs_main);
+ tip = chainActive.Tip();
+ dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
+ dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip);
+ }
+ double gvp = dProgressStart;
while (pindex && !fAbortRescan)
{
- if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0)
- ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((GuessVerificationProgress(chainParams.TxData(), pindex) - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
+ if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) {
+ ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((gvp - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
+ }
if (GetTime() >= nNow + 60) {
nNow = GetTime();
- LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
+ LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, gvp);
}
CBlock block;
if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
+ LOCK2(cs_main, cs_wallet);
+ if (pindex && !chainActive.Contains(pindex)) {
+ // Abort scan if current block is no longer active, to prevent
+ // marking transactions as coming from the wrong block.
+ ret = pindex;
+ break;
+ }
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
}
@@ -1684,14 +1758,21 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
if (pindex == pindexStop) {
break;
}
- pindex = chainActive.Next(pindex);
+ {
+ LOCK(cs_main);
+ pindex = chainActive.Next(pindex);
+ gvp = GuessVerificationProgress(chainParams.TxData(), pindex);
+ if (tip != chainActive.Tip()) {
+ tip = chainActive.Tip();
+ // in case the tip has changed, update progress max
+ dProgressTip = GuessVerificationProgress(chainParams.TxData(), tip);
+ }
+ }
}
if (pindex && fAbortRescan) {
- LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
+ LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, gvp);
}
ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
-
- fScanningWallet = false;
}
return ret;
}
@@ -2174,111 +2255,109 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const
{
+ AssertLockHeld(cs_main);
+ AssertLockHeld(cs_wallet);
+
vCoins.clear();
+ CAmount nTotal = 0;
+ for (const auto& entry : mapWallet)
{
- LOCK2(cs_main, cs_wallet);
+ const uint256& wtxid = entry.first;
+ const CWalletTx* pcoin = &entry.second;
- CAmount nTotal = 0;
+ if (!CheckFinalTx(*pcoin->tx))
+ continue;
- for (const auto& entry : mapWallet)
- {
- const uint256& wtxid = entry.first;
- const CWalletTx* pcoin = &entry.second;
+ if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
+ continue;
- if (!CheckFinalTx(*pcoin->tx))
- continue;
+ int nDepth = pcoin->GetDepthInMainChain();
+ if (nDepth < 0)
+ continue;
- if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
- continue;
+ // We should not consider coins which aren't at least in our mempool
+ // It's possible for these to be conflicted via ancestors which we may never be able to detect
+ if (nDepth == 0 && !pcoin->InMempool())
+ continue;
- int nDepth = pcoin->GetDepthInMainChain();
- if (nDepth < 0)
- continue;
+ bool safeTx = pcoin->IsTrusted();
+
+ // 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 && pcoin->mapValue.count("replaces_txid")) {
+ safeTx = false;
+ }
- // We should not consider coins which aren't at least in our mempool
- // It's possible for these to be conflicted via ancestors which we may never be able to detect
- if (nDepth == 0 && !pcoin->InMempool())
- continue;
+ // 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 && pcoin->mapValue.count("replaced_by_txid")) {
+ safeTx = false;
+ }
- bool safeTx = pcoin->IsTrusted();
-
- // 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 && pcoin->mapValue.count("replaces_txid")) {
- safeTx = false;
- }
+ if (fOnlySafe && !safeTx) {
+ 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 && pcoin->mapValue.count("replaced_by_txid")) {
- safeTx = false;
- }
+ if (nDepth < nMinDepth || nDepth > nMaxDepth)
+ continue;
- if (fOnlySafe && !safeTx) {
+ for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
+ if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount)
continue;
- }
- if (nDepth < nMinDepth || nDepth > nMaxDepth)
+ if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
continue;
- for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
- if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount)
- continue;
-
- if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
- continue;
-
- if (IsLockedCoin(entry.first, i))
- continue;
-
- if (IsSpent(wtxid, i))
- continue;
+ if (IsLockedCoin(entry.first, i))
+ continue;
- isminetype mine = IsMine(pcoin->tx->vout[i]);
+ if (IsSpent(wtxid, i))
+ continue;
- if (mine == ISMINE_NO) {
- continue;
- }
+ isminetype mine = IsMine(pcoin->tx->vout[i]);
- bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO);
- bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO;
+ if (mine == ISMINE_NO) {
+ continue;
+ }
- vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx));
+ bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO);
+ bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO;
- // Checks the sum amount of all UTXO's.
- if (nMinimumSumAmount != MAX_MONEY) {
- nTotal += pcoin->tx->vout[i].nValue;
+ vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx));
- if (nTotal >= nMinimumSumAmount) {
- return;
- }
- }
+ // Checks the sum amount of all UTXO's.
+ if (nMinimumSumAmount != MAX_MONEY) {
+ nTotal += pcoin->tx->vout[i].nValue;
- // Checks the maximum number of UTXO's.
- if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
+ if (nTotal >= nMinimumSumAmount) {
return;
}
}
+
+ // Checks the maximum number of UTXO's.
+ if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
+ return;
+ }
}
}
}
@@ -2296,11 +2375,11 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const
// avoid adding some extra complexity to the Qt code.
std::map<CTxDestination, std::vector<COutput>> result;
-
std::vector<COutput> availableCoins;
- AvailableCoins(availableCoins);
LOCK2(cs_main, cs_wallet);
+ AvailableCoins(availableCoins);
+
for (auto& coin : availableCoins) {
CTxDestination address;
if (coin.fSpendable &&
@@ -2346,171 +2425,88 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out
return ptx->vout[n];
}
-static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
- std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
+bool CWallet::OutputEligibleForSpending(const COutput& output, const CoinEligibilityFilter& eligibility_filter) const
{
- std::vector<char> vfIncluded;
+ if (!output.fSpendable)
+ return false;
- vfBest.assign(vValue.size(), true);
- nBest = nTotalLower;
+ if (output.nDepth < (output.tx->IsFromMe(ISMINE_ALL) ? eligibility_filter.conf_mine : eligibility_filter.conf_theirs))
+ return false;
- FastRandomContext insecure_rand;
+ if (!mempool.TransactionWithinChainLimit(output.tx->GetHash(), eligibility_filter.max_ancestors))
+ return false;
- for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
- {
- vfIncluded.assign(vValue.size(), false);
- CAmount nTotal = 0;
- bool fReachedTarget = false;
- for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
- {
- for (unsigned int i = 0; i < vValue.size(); i++)
- {
- //The solver here uses a randomized algorithm,
- //the randomness serves no real security purpose but is just
- //needed to prevent degenerate behavior and it is important
- //that the rng is fast. We do not use a constant random sequence,
- //because there may be some privacy improvement by making
- //the selection random.
- if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
- {
- nTotal += vValue[i].txout.nValue;
- vfIncluded[i] = true;
- if (nTotal >= nTargetValue)
- {
- fReachedTarget = true;
- if (nTotal < nBest)
- {
- nBest = nTotal;
- vfBest = vfIncluded;
- }
- nTotal -= vValue[i].txout.nValue;
- vfIncluded[i] = false;
- }
- }
- }
- }
- }
+ return true;
}
-bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors, std::vector<COutput> vCoins,
- std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet) const
+bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> vCoins,
+ std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const
{
setCoinsRet.clear();
nValueRet = 0;
- // List of values less than target
- boost::optional<CInputCoin> coinLowestLarger;
- std::vector<CInputCoin> vValue;
- CAmount nTotalLower = 0;
-
- random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
-
- for (const COutput &output : vCoins)
- {
- if (!output.fSpendable)
- continue;
-
- const CWalletTx *pcoin = output.tx;
+ std::vector<CInputCoin> utxo_pool;
+ if (coin_selection_params.use_bnb) {
- if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs))
- continue;
-
- if (!mempool.TransactionWithinChainLimit(pcoin->GetHash(), nMaxAncestors))
- continue;
-
- int i = output.i;
+ // Get long term estimate
+ FeeCalculation feeCalc;
+ CCoinControl temp;
+ temp.m_confirm_target = 1008;
+ CFeeRate long_term_feerate = GetMinimumFeeRate(temp, ::mempool, ::feeEstimator, &feeCalc);
- CInputCoin coin = CInputCoin(pcoin, i);
+ // Calculate cost of change
+ CAmount cost_of_change = GetDiscardRate(::feeEstimator).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size);
- if (coin.txout.nValue == nTargetValue)
+ // Filter by the min conf specs and add to utxo_pool and calculate effective value
+ for (const COutput &output : vCoins)
{
- setCoinsRet.insert(coin);
- nValueRet += coin.txout.nValue;
- return true;
- }
- else if (coin.txout.nValue < nTargetValue + MIN_CHANGE)
- {
- vValue.push_back(coin);
- nTotalLower += coin.txout.nValue;
- }
- else if (!coinLowestLarger || coin.txout.nValue < coinLowestLarger->txout.nValue)
- {
- coinLowestLarger = coin;
- }
- }
-
- if (nTotalLower == nTargetValue)
- {
- for (const auto& input : vValue)
- {
- setCoinsRet.insert(input);
- nValueRet += input.txout.nValue;
- }
- return true;
- }
-
- if (nTotalLower < nTargetValue)
- {
- if (!coinLowestLarger)
- return false;
- setCoinsRet.insert(coinLowestLarger.get());
- nValueRet += coinLowestLarger->txout.nValue;
- return true;
- }
-
- // Solve subset sum by stochastic approximation
- std::sort(vValue.begin(), vValue.end(), CompareValueOnly());
- std::reverse(vValue.begin(), vValue.end());
- std::vector<char> vfBest;
- CAmount nBest;
-
- ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest);
- if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE)
- ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
+ if (!OutputEligibleForSpending(output, eligibility_filter))
+ continue;
- // If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
- // or the next bigger coin is closer), return the bigger coin
- if (coinLowestLarger &&
- ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger->txout.nValue <= nBest))
- {
- setCoinsRet.insert(coinLowestLarger.get());
- nValueRet += coinLowestLarger->txout.nValue;
- }
- else {
- for (unsigned int i = 0; i < vValue.size(); i++)
- if (vfBest[i])
- {
- setCoinsRet.insert(vValue[i]);
- nValueRet += vValue[i].txout.nValue;
+ CInputCoin coin(output.tx->tx, output.i);
+ coin.effective_value = coin.txout.nValue - (output.nInputBytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(output.nInputBytes));
+ // Only include outputs that are positive effective value (i.e. not dust)
+ if (coin.effective_value > 0) {
+ coin.fee = output.nInputBytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(output.nInputBytes);
+ coin.long_term_fee = output.nInputBytes < 0 ? 0 : long_term_feerate.GetFee(output.nInputBytes);
+ utxo_pool.push_back(coin);
}
+ }
+ // Calculate the fees for things that aren't inputs
+ CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size);
+ bnb_used = true;
+ return SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees);
+ } else {
+ // Filter by the min conf specs and add to utxo_pool
+ for (const COutput &output : vCoins)
+ {
+ if (!OutputEligibleForSpending(output, eligibility_filter))
+ continue;
- if (LogAcceptCategory(BCLog::SELECTCOINS)) {
- LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: ");
- for (unsigned int i = 0; i < vValue.size(); i++) {
- if (vfBest[i]) {
- LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(vValue[i].txout.nValue));
- }
- }
- LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
+ CInputCoin coin = CInputCoin(output.tx->tx, output.i);
+ utxo_pool.push_back(coin);
}
+ bnb_used = false;
+ return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet);
}
-
- return true;
}
-bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const
+bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const
{
std::vector<COutput> vCoins(vAvailableCoins);
// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
- if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs)
+ if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs)
{
+ // We didn't use BnB here, so set it to false.
+ bnb_used = false;
+
for (const COutput& out : vCoins)
{
if (!out.fSpendable)
continue;
nValueRet += out.tx->tx->vout[out.i].nValue;
- setCoinsRet.insert(CInputCoin(out.tx, out.i));
+ setCoinsRet.insert(CInputCoin(out.tx->tx, out.i));
}
return (nValueRet >= nTargetValue);
}
@@ -2520,10 +2516,12 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
CAmount nValueFromPresetInputs = 0;
std::vector<COutPoint> vPresetInputs;
- if (coinControl)
- coinControl->ListSelected(vPresetInputs);
+ coin_control.ListSelected(vPresetInputs);
for (const COutPoint& outpoint : vPresetInputs)
{
+ // For now, don't use BnB if preset inputs are selected. TODO: Enable this later
+ bnb_used = false;
+
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
if (it != mapWallet.end())
{
@@ -2531,16 +2529,17 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
// Clearly invalid input, fail
if (pcoin->tx->vout.size() <= outpoint.n)
return false;
+ // Just to calculate the marginal byte size
nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue;
- setPresetCoins.insert(CInputCoin(pcoin, outpoint.n));
+ setPresetCoins.insert(CInputCoin(pcoin->tx, outpoint.n));
} else
return false; // TODO: Allow non-wallet inputs
}
// remove preset inputs from vCoins
- for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();)
+ for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
{
- if (setPresetCoins.count(CInputCoin(it->tx, it->i)))
+ if (setPresetCoins.count(CInputCoin(it->tx->tx, it->i)))
it = vCoins.erase(it);
else
++it;
@@ -2550,13 +2549,13 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
bool res = nTargetValue <= nValueFromPresetInputs ||
- SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 6, 0, vCoins, setCoinsRet, nValueRet) ||
- SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, 0, vCoins, setCoinsRet, nValueRet) ||
- (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, 2, vCoins, setCoinsRet, nValueRet)) ||
- (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, std::min((size_t)4, nMaxChainLength/3), vCoins, setCoinsRet, nValueRet)) ||
- (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength/2, vCoins, setCoinsRet, nValueRet)) ||
- (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength, vCoins, setCoinsRet, nValueRet)) ||
- (bSpendZeroConfChange && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, std::numeric_limits<uint64_t>::max(), vCoins, setCoinsRet, nValueRet));
+ SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
+ SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
+ (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, nMaxChainLength/3)), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, nMaxChainLength/2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, nMaxChainLength), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (bSpendZeroConfChange && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end());
@@ -2613,13 +2612,13 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
LOCK2(cs_main, cs_wallet);
CReserveKey reservekey(this);
- CWalletTx wtx;
- if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) {
+ CTransactionRef tx_new;
+ if (!CreateTransaction(vecSend, tx_new, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) {
return false;
}
if (nChangePosInOut != -1) {
- tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]);
+ tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]);
// We don't have the normal Create/Commit cycle, and don't want to risk
// reusing change, so just remove the key from the keypool here.
reservekey.KeepKey();
@@ -2628,11 +2627,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
// 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;
+ tx.vout[idx].nValue = tx_new->vout[idx].nValue;
}
// Add new txins while keeping original txin scriptSig/order.
- for (const CTxIn& txin : wtx.tx->vin) {
+ for (const CTxIn& txin : tx_new->vin) {
if (!coinControl.IsSelected(txin.prevout)) {
tx.vin.push_back(txin);
@@ -2645,7 +2644,35 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
return true;
}
-bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
+OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend)
+{
+ // If -changetype is specified, always use that change type.
+ if (change_type != OutputType::NONE) {
+ return change_type;
+ }
+
+ // if m_default_address_type is legacy, use legacy address as change (even
+ // if some of the outputs are P2WPKH or P2WSH).
+ if (m_default_address_type == OutputType::LEGACY) {
+ return OutputType::LEGACY;
+ }
+
+ // if any destination is P2WPKH or P2WSH, use P2WPKH for the change
+ // output.
+ for (const auto& recipient : vecSend) {
+ // Check if any destination contains a witness program:
+ int witnessversion = 0;
+ std::vector<unsigned char> witnessprogram;
+ if (recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
+ return OutputType::BECH32;
+ }
+ }
+
+ // else use m_default_address_type for change
+ return m_default_address_type;
+}
+
+bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet,
int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign)
{
CAmount nValue = 0;
@@ -2669,8 +2696,6 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
return false;
}
- wtxNew.fTimeReceivedIsTxTime = true;
- wtxNew.BindWallet(this);
CMutableTransaction txNew;
// Discourage fee sniping.
@@ -2706,13 +2731,14 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
FeeCalculation feeCalc;
CAmount nFeeNeeded;
- unsigned int nBytes;
+ int nBytes;
{
std::set<CInputCoin> setCoins;
LOCK2(cs_main, cs_wallet);
{
std::vector<COutput> vAvailableCoins;
AvailableCoins(vAvailableCoins, true, &coin_control);
+ CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
// Create change script that will be used if we need change
// TODO: pass in scriptChange instead of reservekey so
@@ -2740,28 +2766,40 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
return false;
}
- scriptChange = GetScriptForDestination(vchPubKey.GetID());
+ const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
+
+ LearnRelatedScripts(vchPubKey, change_type);
+ scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, change_type));
}
CTxOut change_prototype_txout(0, scriptChange);
- size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0);
+ coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0);
CFeeRate discard_rate = GetDiscardRate(::feeEstimator);
+
+ // Get the fee rate to use effective values in coin selection
+ CFeeRate nFeeRateNeeded = GetMinimumFeeRate(coin_control, ::mempool, ::feeEstimator, &feeCalc);
+
nFeeRet = 0;
bool pick_new_inputs = true;
CAmount nValueIn = 0;
+
+ // BnB selector is the only selector used when this is true.
+ // That should only happen on the first pass through the loop.
+ coin_selection_params.use_bnb = nSubtractFeeFromAmount == 0; // If we are doing subtract fee from recipient, then don't use BnB
// Start with no fee and loop until there is enough fee
while (true)
{
nChangePosInOut = nChangePosRequest;
txNew.vin.clear();
txNew.vout.clear();
- wtxNew.fFromMe = true;
bool fFirst = true;
CAmount nValueToSelect = nValue;
if (nSubtractFeeFromAmount == 0)
nValueToSelect += nFeeRet;
+
// vouts to the payees
+ coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size)
for (const auto& recipient : vecSend)
{
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
@@ -2777,6 +2815,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
}
}
+ // Include the fee cost for outputs. Note this is only used for BnB right now
+ coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, SER_NETWORK, PROTOCOL_VERSION);
if (IsDust(txout, ::dustRelayFee))
{
@@ -2795,18 +2835,27 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
}
// Choose coins to use
+ bool bnb_used;
if (pick_new_inputs) {
nValueIn = 0;
setCoins.clear();
- if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, &coin_control))
+ coin_selection_params.change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
+ coin_selection_params.effective_fee = nFeeRateNeeded;
+ if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used))
{
- strFailReason = _("Insufficient funds");
- return false;
+ // If BnB was used, it was the first pass. No longer the first pass and continue loop with knapsack.
+ if (bnb_used) {
+ coin_selection_params.use_bnb = false;
+ continue;
+ }
+ else {
+ strFailReason = _("Insufficient funds");
+ return false;
+ }
}
}
const CAmount nChange = nValueIn - nValueToSelect;
-
if (nChange > 0)
{
// Fill a vout to ourself
@@ -2814,7 +2863,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
// Never create dust outputs; if we would, just
// add the dust to the fee.
- if (IsDust(newTxOut, discard_rate))
+ // The nChange when BnB is used is always going to go to fees.
+ if (IsDust(newTxOut, discard_rate) || bnb_used)
{
nChangePosInOut = -1;
nFeeRet += nChange;
@@ -2854,21 +2904,18 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
txNew.vin.push_back(CTxIn(coin.outpoint,CScript(),
nSequence));
- // Fill in dummy signatures for fee calculation.
- if (!DummySignTx(txNew, setCoins)) {
+ nBytes = CalculateMaximumSignedTxSize(txNew, this);
+ if (nBytes < 0) {
strFailReason = _("Signing transaction failed");
return false;
}
- nBytes = GetVirtualTransactionSize(txNew);
-
- // Remove scriptSigs to eliminate the fee calculation dummy signatures
- for (auto& vin : txNew.vin) {
- vin.scriptSig = CScript();
- vin.scriptWitness.SetNull();
- }
-
nFeeNeeded = GetMinimumFee(nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc);
+ if (feeCalc.reason == FeeReason::FALLBACK && !g_wallet_allow_fallback_fee) {
+ // eventually allow a fallback fee
+ strFailReason = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
+ return false;
+ }
// If we made it here and we aren't even able to meet the relay fee on the next pass, give up
// because we must be at the maximum allowed fee.
@@ -2890,7 +2937,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
// (because of reduced tx size) and so we should add a
// change output. Only try this once.
if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) {
- unsigned int tx_size_with_change = nBytes + change_prototype_size + 2; // Add 2 as a buffer in case increasing # of outputs changes compact size
+ unsigned int tx_size_with_change = nBytes + coin_selection_params.change_output_size + 2; // Add 2 as a buffer in case increasing # of outputs changes compact size
CAmount fee_needed_with_change = GetMinimumFee(tx_size_with_change, coin_control, ::mempool, ::feeEstimator, nullptr);
CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, discard_rate);
if (nFeeRet >= fee_needed_with_change + minimum_value_for_change) {
@@ -2938,6 +2985,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
// Include more fee and try again.
nFeeRet = nFeeNeeded;
+ coin_selection_params.use_bnb = false;
continue;
}
}
@@ -2965,11 +3013,11 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
}
}
- // Embed the constructed transaction data in wtxNew.
- wtxNew.SetTx(MakeTransactionRef(std::move(txNew)));
+ // Return the constructed transaction data.
+ tx = MakeTransactionRef(std::move(txNew));
// Limit size
- if (GetTransactionWeight(*wtxNew.tx) >= MAX_STANDARD_TX_WEIGHT)
+ if (GetTransactionWeight(*tx) >= MAX_STANDARD_TX_WEIGHT)
{
strFailReason = _("Transaction too large");
return false;
@@ -2979,7 +3027,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits
LockPoints lp;
- CTxMemPoolEntry entry(wtxNew.tx, 0, 0, 0, false, 0, lp);
+ CTxMemPoolEntry entry(tx, 0, 0, 0, false, 0, lp);
CTxMemPool::setEntries setAncestors;
size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
@@ -3006,10 +3054,18 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
/**
* Call after CreateTransaction unless you want to abort
*/
-bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state)
+bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, std::string fromAccount, CReserveKey& reservekey, CConnman* connman, CValidationState& state)
{
{
LOCK2(cs_main, cs_wallet);
+
+ CWalletTx wtxNew(this, std::move(tx));
+ wtxNew.mapValue = std::move(mapValue);
+ wtxNew.vOrderForm = std::move(orderForm);
+ wtxNew.strFromAccount = std::move(fromAccount);
+ wtxNew.fTimeReceivedIsTxTime = true;
+ wtxNew.fFromMe = true;
+
LogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString());
{
// Take key pair from key pool so it won't be used again
@@ -3022,7 +3078,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon
// Notify that old coins are spent
for (const CTxIn& txin : wtxNew.tx->vin)
{
- CWalletTx &coin = mapWallet[txin.prevout.hash];
+ CWalletTx &coin = mapWallet.at(txin.prevout.hash);
coin.BindWallet(this);
NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED);
}
@@ -3033,13 +3089,13 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon
// Get the inserted-CWalletTx from mapWallet so that the
// fInMempool flag is cached properly
- CWalletTx& wtx = mapWallet[wtxNew.GetHash()];
+ CWalletTx& wtx = mapWallet.at(wtxNew.GetHash());
if (fBroadcastTransactions)
{
// Broadcast
if (!wtx.AcceptToMemoryPool(maxTxFee, state)) {
- LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", state.GetRejectReason());
+ LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state));
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
} else {
wtx.RelayWalletTransaction(connman);
@@ -3489,7 +3545,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings()
CTxDestination address;
if(!IsMine(txin)) /* If this input isn't mine, ignore it */
continue;
- if(!ExtractDestination(mapWallet[txin.prevout.hash].tx->vout[txin.prevout.n].scriptPubKey, address))
+ if(!ExtractDestination(mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address))
continue;
grouping.insert(address);
any_mine = true;
@@ -3629,6 +3685,7 @@ void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id)
if (walletdb.ReadPool(index, keypool)) { //TODO: This should be unnecessary
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
}
+ LearnAllRelatedScripts(keypool.vchPubKey);
walletdb.ErasePool(index);
LogPrintf("keypool index %d removed\n", index);
it = setKeyPool->erase(it);
@@ -3712,10 +3769,10 @@ void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) c
for (const auto& entry : mapWallet) {
// iterate over all wallet transactions...
const CWalletTx &wtx = entry.second;
- BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock);
- if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) {
+ CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock);
+ if (pindex && chainActive.Contains(pindex)) {
// ... which are already in a block
- int nHeight = blit->second->nHeight;
+ int nHeight = pindex->nHeight;
for (const CTxOut &txout : wtx.tx->vout) {
// iterate over all their outputs
CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
@@ -3723,7 +3780,7 @@ void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) c
// ... and all their affected keys
std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight)
- rit->second = blit->second;
+ rit->second = pindex;
}
vAffected.clear();
}
@@ -3760,7 +3817,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
{
unsigned int nTimeSmart = wtx.nTimeReceived;
if (!wtx.hashUnset()) {
- if (mapBlockIndex.count(wtx.hashBlock)) {
+ if (const CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock)) {
int64_t latestNow = wtx.nTimeReceived;
int64_t latestEntry = 0;
@@ -3791,7 +3848,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
}
}
- int64_t blocktime = mapBlockIndex[wtx.hashBlock]->GetBlockTime();
+ int64_t blocktime = pindex->GetBlockTime();
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
} else {
LogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString());
@@ -3852,16 +3909,17 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
return values;
}
-CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
+CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path)
{
+ const std::string& walletFile = name;
+
// needed to restore wallet transaction meta data after -zapwallettxes
std::vector<CWalletTx> vWtx;
if (gArgs.GetBoolArg("-zapwallettxes", false)) {
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
- std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, walletFile));
- std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(std::move(dbw));
+ std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(name, CWalletDBWrapper::Create(path));
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
if (nZapWalletRet != DB_LOAD_OK) {
InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
@@ -3873,8 +3931,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
int64_t nStart = GetTimeMillis();
bool fFirstRun = true;
- std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, walletFile));
- CWallet *walletInstance = new CWallet(std::move(dbw));
+ CWallet *walletInstance = new CWallet(name, CWalletDBWrapper::Create(path));
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
if (nLoadWalletRet != DB_LOAD_OK)
{
@@ -3943,8 +4000,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
}
walletInstance->SetBestChain(chainActive.GetLocator());
- }
- else if (gArgs.IsArgSet("-usehd")) {
+ } else if (gArgs.IsArgSet("-usehd")) {
bool useHD = gArgs.GetBoolArg("-usehd", true);
if (walletInstance->IsHDEnabled() && !useHD) {
InitError(strprintf(_("Error loading %s: You can't disable HD on an already existing HD wallet"), walletFile));
@@ -3956,11 +4012,27 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
}
}
+ walletInstance->m_default_address_type = ParseOutputType(gArgs.GetArg("-addresstype", ""), DEFAULT_ADDRESS_TYPE);
+ if (walletInstance->m_default_address_type == OutputType::NONE) {
+ InitError(strprintf("Unknown address type '%s'", gArgs.GetArg("-addresstype", "")));
+ return nullptr;
+ }
+
+ // If changetype is set in config file or parameter, check that it's valid.
+ // Default to OutputType::NONE if not set.
+ walletInstance->m_default_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), OutputType::NONE);
+ if (walletInstance->m_default_change_type == OutputType::NONE && !gArgs.GetArg("-changetype", "").empty()) {
+ InitError(strprintf("Unknown change type '%s'", gArgs.GetArg("-changetype", "")));
+ return nullptr;
+ }
+
LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart);
// Try to top up keypool. No-op if the wallet is locked.
walletInstance->TopUpKeyPool();
+ LOCK(cs_main);
+
CBlockIndex *pindexRescan = chainActive.Genesis();
if (!gArgs.GetBoolArg("-rescan", false))
{
@@ -4000,7 +4072,14 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
}
nStart = GetTimeMillis();
- walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, true);
+ {
+ WalletRescanReserver reserver(walletInstance);
+ if (!reserver.reserve()) {
+ InitError(_("Failed to rescan the wallet during initialization"));
+ return nullptr;
+ }
+ walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true);
+ }
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
walletInstance->SetBestChain(chainActive.GetLocator());
walletInstance->dbw->IncrementUpdateCounter();
@@ -4097,10 +4176,7 @@ int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
AssertLockHeld(cs_main);
// Find the block it claims to be in
- BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
- if (mi == mapBlockIndex.end())
- return 0;
- CBlockIndex* pindex = (*mi).second;
+ CBlockIndex* pindex = LookupBlockIndex(hashBlock);
if (!pindex || !chainActive.Contains(pindex))
return 0;
@@ -4118,18 +4194,116 @@ int CMerkleTx::GetBlocksToMaturity() const
bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state)
{
- // Quick check to avoid re-setting fInMempool to false
- if (mempool.exists(tx->GetHash())) {
- return false;
- }
-
// We must set fInMempool here - while it will be re-set to true by the
// entered-mempool callback, if we did not there would be a race where a
// user could call sendmoney in a loop and hit spurious out of funds errors
- // because we think that the transaction they just generated's change is
- // unavailable as we're not yet aware its in mempool.
+ // because we think that this newly generated transaction's change is
+ // unavailable as we're not yet aware that it is in the mempool.
bool ret = ::AcceptToMemoryPool(mempool, state, tx, nullptr /* pfMissingInputs */,
nullptr /* plTxnReplaced */, false /* bypass_limits */, nAbsurdFee);
- fInMempool = ret;
+ fInMempool |= ret;
return ret;
}
+
+static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy";
+static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit";
+static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32";
+
+OutputType ParseOutputType(const std::string& type, OutputType default_type)
+{
+ if (type.empty()) {
+ return default_type;
+ } else if (type == OUTPUT_TYPE_STRING_LEGACY) {
+ return OutputType::LEGACY;
+ } else if (type == OUTPUT_TYPE_STRING_P2SH_SEGWIT) {
+ return OutputType::P2SH_SEGWIT;
+ } else if (type == OUTPUT_TYPE_STRING_BECH32) {
+ return OutputType::BECH32;
+ } else {
+ return OutputType::NONE;
+ }
+}
+
+const std::string& FormatOutputType(OutputType type)
+{
+ switch (type) {
+ case OutputType::LEGACY: return OUTPUT_TYPE_STRING_LEGACY;
+ case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT;
+ case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32;
+ default: assert(false);
+ }
+}
+
+void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type)
+{
+ if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) {
+ CTxDestination witdest = WitnessV0KeyHash(key.GetID());
+ CScript witprog = GetScriptForDestination(witdest);
+ // Make sure the resulting program is solvable.
+ assert(IsSolvable(*this, witprog));
+ AddCScript(witprog);
+ }
+}
+
+void CWallet::LearnAllRelatedScripts(const CPubKey& key)
+{
+ // OutputType::P2SH_SEGWIT always adds all necessary scripts for all types.
+ LearnRelatedScripts(key, OutputType::P2SH_SEGWIT);
+}
+
+CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type)
+{
+ switch (type) {
+ case OutputType::LEGACY: return key.GetID();
+ case OutputType::P2SH_SEGWIT:
+ case OutputType::BECH32: {
+ if (!key.IsCompressed()) return key.GetID();
+ CTxDestination witdest = WitnessV0KeyHash(key.GetID());
+ CScript witprog = GetScriptForDestination(witdest);
+ if (type == OutputType::P2SH_SEGWIT) {
+ return CScriptID(witprog);
+ } else {
+ return witdest;
+ }
+ }
+ default: assert(false);
+ }
+}
+
+std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key)
+{
+ CKeyID keyid = key.GetID();
+ if (key.IsCompressed()) {
+ CTxDestination segwit = WitnessV0KeyHash(keyid);
+ CTxDestination p2sh = CScriptID(GetScriptForDestination(segwit));
+ return std::vector<CTxDestination>{std::move(keyid), std::move(p2sh), std::move(segwit)};
+ } else {
+ return std::vector<CTxDestination>{std::move(keyid)};
+ }
+}
+
+CTxDestination CWallet::AddAndGetDestinationForScript(const CScript& script, OutputType type)
+{
+ // Note that scripts over 520 bytes are not yet supported.
+ switch (type) {
+ case OutputType::LEGACY:
+ return CScriptID(script);
+ case OutputType::P2SH_SEGWIT:
+ case OutputType::BECH32: {
+ WitnessV0ScriptHash hash;
+ CSHA256().Write(script.data(), script.size()).Finalize(hash.begin());
+ CTxDestination witdest = hash;
+ CScript witprog = GetScriptForDestination(witdest);
+ // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key)
+ if (!IsSolvable(*this, witprog)) return CScriptID(script);
+ // Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours.
+ AddCScript(witprog);
+ if (type == OutputType::BECH32) {
+ return witdest;
+ } else {
+ return CScriptID(witprog);
+ }
+ }
+ default: assert(false);
+ }
+}